# jvm

jvm 概要

# jvm 执行Java 文件流程

**问题:**

- **虚拟机如何执行Java代码的？**
    - **通过把字节码转换成汇编代码保存到内存，修改指令代码执行？**
    -

```c++
int JavaMain(void* _args)
{
    JavaMainArgs *args = (JavaMainArgs *)_args;
    int argc = args->argc;
    char **argv = args->argv;
    int mode = args->mode;
    char *what = args->what;
    InvocationFunctions ifn = args->ifn;

    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jclass mainClass = NULL;
    jclass appClass = NULL; // 正在启动的实际应用程序类
    jmethodID mainID;
    jobjectArray mainArgs;
    int ret = 0;
    jlong start = 0, end = 0;

    RegisterThread();

    /* 初始化虚拟机 */
    start = CurrentTimeMicros();
    if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }

    if (showSettings != NULL) {
        ShowSettings(env, showSettings);
        CHECK_EXCEPTION_LEAVE(1);
    }

    // 显示已解析的模块并继续
    if (showResolvedModules) {
        ShowResolvedModules(env);
        CHECK_EXCEPTION_LEAVE(1);
    }

    // 列出可观察模块，然后退出
    if (listModules) {
        ListModules(env);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }

    // 描述模块，然后退出
    if (describeModule != NULL) {
        DescribeModule(env, describeModule);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }

    if (printVersion || showVersion) {
        PrintJavaVersion(env, showVersion);
        CHECK_EXCEPTION_LEAVE(0);
        if (printVersion) {
            LEAVE();
        }
    }

    // 模块已在启动时验证，因此退出
    if (validateModules) {
        LEAVE();
    }

    /* 如果用户既没有指定类名也没有指定JAR文件 */
    if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
        PrintUsage(env, printXUsage);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }

    FreeKnownVMs(); /* 上次可能的打印使用之后 */
    // JLI是跟踪启动器?
    if (JLI_IsTraceLauncher()) {
        end = CurrentTimeMicros();
        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n", (long)(end-start));
    }

    /* 在此阶段，argc/argv具有应用程序的参数 */
    if (JLI_IsTraceLauncher()){
        int i;
        printf("%s is '%s'\n", launchModeNames[mode], what);
        printf("App's argc is %d\n", argc);
        for (i=0; i < argc; i++) {
            printf("    argv[%2d] = '%s'\n", i, argv[i]);
        }
    }

    ret = 1;

    /*
     * 获取应用程序的主类。它还检查主方法是否存在。
     *
     * See bugid 5030265.  
     * 已经从清单中解析了Main类名，但没有正确解析以支持UTF-8。
     * 因此，这里的代码会忽略先前提取的值，并使用预先存在的代码重新提取该值。
     * 这可能是发布周期结束的权宜之计。然而，人们也发现，在Windows的一些变体上，通过环境传递一些字符集会有“奇怪”的行为。 
     * 因此，可能永远不应该增强启动器本地的清单解析代码。
     *
     * 因此，未来的工作应该:
     *     1) 更正本地解析代码，并验证Main Class属性是否在所有环境中正确传递,
     *     2) 删除通过环境维护main类的遗留问题（并删除这些注释）。
     * 此方法还正确地处理启动现有的Java FX应用程序，这些应用程序可能有Main Class清单条目，也可能没有。
     */
    mainClass = LoadMainClass(env, mode, what);
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    /*
     * 在一些情况下。，
     * 一个没有main方法的JavaFX应用程序，main Class将不是应用程序自己的主类，而是一个辅助类。
     * 为了在UI中保持一致，我们需要跟踪和报告应用程序主类。
     */
    appClass = GetApplicationClass(env);
    NULL_CHECK_RETURN_VALUE(appClass, -1);

    /* 生成特定于平台的参数数组 */
    mainArgs = CreateApplicationArgs(env, argv, argc);
    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);

    if (dryRun) {
        ret = 0;
        LEAVE();
    }

    /*
     * PostJVMInit 将类名用作GUI用途的应用程序名称,
     * 例如，在OSX上，这将在SWT和JavaFX的菜单栏中设置应用程序名称。
     * 因此，我们将在这里传递实际的应用程序类，而不是主类，因为它可能是一个启动器或帮助程序类而不是应用程序类。
     */
    PostJVMInit(env, appClass, vm);
    CHECK_EXCEPTION_LEAVE(1);

    /**
     * Load Main Class不仅加载主类，还将确保主方法的签名是正确的，因此不需要进一步检查。
     * 这里调用main方法，以便在应用程序堆栈跟踪中不存在无关的java堆栈。
     */
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    CHECK_EXCEPTION_NULL_LEAVE(mainID);

    /* Invoke主方法. 调用java main 函数 */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

    /*
     * 如果main抛出异常，启动器的退出代码（在没有调用System.exit的情况下）将为非零。
     */
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;

    LEAVE();
}

```

# generate_normal_entry

1. 确定代码生成标志
2. 获取入口点地址
3. 常量函数地址
4. 访问标志地址
5. 参数数量
6. 本地变量数量
7. 获取参数shu'l

# jvm 运行图

[![jvm-run.png](http://wiki.shopqorg.com/uploads/images/gallery/2023-11/scaled-1680-/jvm-run.png)](http://wiki.shopqorg.com/uploads/images/gallery/2023-11/jvm-run.png)

[![jvm.png](http://wiki.shopqorg.com/uploads/images/gallery/2023-11/scaled-1680-/jvm.png)](http://wiki.shopqorg.com/uploads/images/gallery/2023-11/jvm.png)

# 影子页面

**影子页面（Shadow Page）是指在虚拟化技术中，为了实现虚拟机对物理机的模拟，而在虚拟机中创建的与物理机页面相对应的页面。**影子页面通常由虚拟机监控器（Hypervisor）根据物理机的页面表而生成，用于保存物理机的页面数据，以便在虚拟机中访问物理机时进行地址转换和页面映射。

在虚拟机运行时，操作系统会将虚拟地址转换为物理地址，以访问物理机的内存和硬件资源。这个转换过程中，影子页面起到了关键的作用。当虚拟机尝试访问一个虚拟地址时，会触发一个页面异常，这时虚拟机监控器会检查影子页面表，以确定该虚拟地址所对应的物理地址。如果影子页面表中存在该物理地址的影子页面，那么虚拟机就可以继续执行；否则，虚拟机监控器会将该影子页面加载到物理内存中，并将物理地址写入影子页面表，以供虚拟机继续访问。

影子页面的使用可以保证虚拟机的隔离和安全性，因为每个虚拟机都有自己的影子页面表，并且影子页面表对虚拟机是透明的。这样就可以防止虚拟机直接访问物理内存和其他资源，从而保护宿主机和虚拟机的安全。

# 预留/黄色区域

**当我们重新进入Java时，我们需要重新启用在虚拟机中可能已经被禁用的预留/黄色区域**。  
 \*   
 \* 在虚拟机中，**预留区域（Reserved Zone）的作用主要是为了防止栈溢出的攻击**。这个区域是在Java堆栈中设置的一个安全地带，用于保护程序的安全运行。  
 \* 如果线程请求的堆栈大小超过了当前堆栈的容量，Java虚拟机会抛出StackOverflowError异常。  
 \* 在这种情况下，如果存在预留区域，Java虚拟机就会在这个区域内分配新的堆栈，以容纳更多线程。  
 \* 这样，即使在堆栈溢出的情况下，程序也能继续运行，而不会出现异常或崩溃。  
 \* 需要注意的是，预留区域的大小是动态变化的，它会根据线程请求的堆栈大小进行调整。  
 \* 如果请求的堆栈大小超出了当前预留区域的容量，Java虚拟机会尝试扩展预留区域的大小，以满足线程的需求。  
 \* 此外，预留区域还可以用于实现一些特殊的功能，比如实现线程的本地存储（Thread-local storage），  
 \* 为每个线程分配独立的内存空间，以存储线程的本地变量和数据。这  
 \* 样可以避免不同线程之间的数据干扰和冲突，保证程序的安全性和稳定性。  
 \*   
 \* **黄色区域指的是线程私有的三个模块**，  
 \* 即虚拟机栈、  
 \* 本地方法栈  
 \* 程序计数器。  
 \* 这些区域是隔离的，每个线程都有自己的私有实例，不允许其他线程访问。  
 \* 这种隔离有助于保护线程数据的安全性和独立性。  
 \* 虚拟机规范将这些区域划分为黄色，是为了强调它们是线程私有的，与共享的绿色区域相区别。  
 \* **绿色区域是线程共享的数据区域，可以被多个线程共同访问和修改。**  
 \* 这种划分使得虚拟机能够更好地管理线程之间的协作和并发操作，从而提高程序的性能和安全性。  
 \* 总之，黄色区域是虚拟机中线程私有的内存区域，用于存储线程的私有数据和执行线程的操作。  
 \* 这种隔离有助于保护线程数据的安全性和独立性。

# jvm 常量池类型说明

[![image.png](http://wiki.shopqorg.com/uploads/images/gallery/2023-11/scaled-1680-/K2Mimage.png)](http://wiki.shopqorg.com/uploads/images/gallery/2023-11/K2Mimage.png)

# 新页面

<div drawio-diagram="131"><img src="http://wiki.shopqorg.com/uploads/images/drawio/2024-01/drawing-1-1704865779.png" alt=""/></div>