从OpenJDK源码看JAVA虚拟机的创建过程

关于Java跨平台能力的理解

Java语言是一种跨平台的语言,大家通常都这样描述Java语言:Write Once, Run Anywhere,即一次编写,到处运行。

理论上来讲,一个Java程序只需要编写并编译一次,就可以在任何合适版本的JVM设备上运行。

之所以能跨平台,这主要是因为它采用了一种独特的运行时环境和字节码概念。

  • 字节码
    Java源代码首先被编译成一种中间形式,称为字节码(.class 文件)。这种字节码不是针对任何特定的硬件或操作系统编写的,它是一种特殊类型的代码,可以在任何实现了Java虚拟机(JVM)的设备上运行。
  • Java虚拟机(JVM)
    运行Java程序时,不是直接在操作系统上运行,而是在JVM上运行。JVM是一个软件层,工作在特定操作系统上面。JVM读取字节码并且转换(或解释)成特定平台上的本地代码。每个操作系统平台都有自己的JVM实现,这使得任何平台上的JVM都能理解相同的Java字节码。

今天我们主要是走读一下OpenJDK的源码,来看一下Java虚拟机的创建过程,了解了虚拟机的创建过程,为后面更好的理解类的加载和创建打下基础。

Java Virtual Machine是怎么创建的。

通常我们运行一个Java应用程序是通过以下的方式:

java -Xmx1024m -Xms512m -jar my_application.jar

当我们执行这个条java命令后,就会启动一个Java虚拟机来运行我们的程序。

接下来我们就从源码的角度,一步一步来分析这条命令背后都有哪些细节。

1. Java Launcher

没错,当我们执行java命令时,这个java命令就叫做Java Launcher,它负责执行与启动 JVM 实例、加载类、初始化 Java 环境等操作。

它的源代码位于:/jdk-master/src/java.base/share/native/launcher目录,main.c就是java命令的源代码了。我们看到代码的最后,调用了一个JLI_Launch的方法。
Java Launcher入口

2. JLI_Launch 入口

源码位置:/jdk-master/src/java.base/share/native/libjli/java.c
JLI_Launch入口
它主要做的工作是解析命令行参数,设置JVM环境,加载Java虚拟机,并启动Java应用程序。

在这个方法的最后面,会调用JVMInit方法,来初始化Java虚拟机。

    /* Override class path if -jar flag was specified */
    if (mode == LM_JAR) {
        SetClassPath(what);     /* Override class path */
    }

    /* set the -Dsun.java.command pseudo property */
    SetJavaCommandLineProp(what, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */
    SetJavaLauncherProp();

    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);

3. JVM-Init

源码位置:/jdk-master/src/java.base/unix/native/libjli/java_md.c
JVM-Init
我们看到这时,会开启一个新的线程来继续JVM的初始化工作。

4. 开启新线程并继续

又回到了libjli中的java.c文件
源码位置:/jdk-master/src/java.base/share/native/libjli/java.c
CallJavaMain

5. 调用JavaMain

回到/jdk-master/src/java.base/unix/native/libjli/java_md.c文件,

  • CallJavaMainInNewThread 方法实现
    • 最底下调用JavaMain()方法

Call JavaMain

6. 初始化Java虚拟机,并执行Main方法

终于进入了主题,JavaMain方法会开启一个新的线程,并今行JVM的初始化工作。
源码位置:/jdk-master/src/java.base/share/native/libjli/java.c

初始化虚拟机

java.c中的InitializeJVM 方法

/*
 * Initializes the Java Virtual Machine. Also frees options array when
 * finished.
 */
static jboolean
InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
    JavaVMInitArgs args;
    jint r;

    memset(&args, 0, sizeof(args));
    args.version  = JNI_VERSION_1_2;
    args.nOptions = numOptions;
    args.options  = options;
    args.ignoreUnrecognized = JNI_FALSE;

    if (JLI_IsTraceLauncher()) {
        int i = 0;
        printf("JavaVM args:\n    ");
        printf("version 0x%08lx, ", (long)args.version);
        printf("ignoreUnrecognized is %s, ",
               args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
        printf("nOptions is %ld\n", (long)args.nOptions);
        for (i = 0; i < numOptions; i++)
            printf("    option[%2d] = '%s'\n",
                   i, args.options[i].optionString);
    }

    r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
    JLI_MemFree(options);
    return r == JNI_OK;
}

看们看倒数第三行代码,调用CreateJavaVM方法,来创建虚拟机。

//这里 ifn->,是C语言中结构体指针访问结构体的一种语法。
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);

//ifn的声明在另外一个文件中
//源码位置:/jdk-master/src/java.base/share/native/libjli/java.h
typedef struct {
    int    argc;
    char **argv;
    int    mode;
    char  *what;
    InvocationFunctions ifn;
} JavaMainArgs;

//在动态链接库 (Dynamic Linking Library, DLL) 中查找名为 "JNI_CreateJavaVM" 的函数,并将其地址赋值给 ifn->CreateJavaVM
//该代码片段位于LoadJavaVM()方法中,而LoadJavaVM()目的是动态加载Java虚拟机(JVM)的库(libjvm.so或jvm.dll等,取决于操作系统)并查找JNI(Java Native Interface)相关的几个关键函数指针,使得C或C++代码能够调用这些函数创建和操作Java虚拟机。
ifn->CreateJavaVM = (CreateJavaVM_t)
       dlsym(libjvm, "JNI_CreateJavaVM");
   if (ifn->CreateJavaVM == NULL) {
       JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
       return JNI_FALSE;
}

7. JNI_CreateJavaVM

即将调用创建虚拟机的真正方法:Threads::create_vm()
源码位置:/jdk-master/src/hotspot/share/prims/jni.cpp

_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
  jint result = JNI_ERR;
  // On Windows, let CreateJavaVM run with SEH protection
#if defined(_WIN32) && !defined(USE_VECTORED_EXCEPTION_HANDLING)
  __try {
#endif
    result = JNI_CreateJavaVM_inner(vm, penv, args);
#if defined(_WIN32) && !defined(USE_VECTORED_EXCEPTION_HANDLING)
  } __except(topLevelExceptionFilter((_EXCEPTION_POINTERS*)_exception_info())) {
    // Nothing to do.
  }
#endif
  return result;
}


//JNI_CreateJavaVM_inner 同样位于 jni.cpp中,代码片段如下:
static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
  HOTSPOT_JNI_CREATEJAVAVM_ENTRY((void **) vm, penv, args);

  jint result = JNI_ERR;
  //此处省略部分代码
  //创建虚拟机的关键方法调用:Threads::create_vm()
  result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
  

8. 虚拟机创建

源码位置: /jdk-master/src/hotspot/share/runtime/threads.cpp
整个Threads::create_vm方法,从413行开始,832行结束,另外还调用了一些其它重要的初始化方法
虚拟机创建过程

初始化过程概览

由于内容太多,我们只看几个关键的地方。


initialize_java_lang_classes(main_thread, CHECK_JNI_ERR);

//此方法由initialize_java_lang_classes方法调用
// Phase 1 of the system initialization in the library, java.lang.System class initialization
  call_initPhase1(CHECK);
// The system initialization in the library has three phases.
//
// Phase 1: java.lang.System class initialization
//     java.lang.System is a primordial class loaded and initialized
//     by the VM early during startup.  java.lang.System.<clinit>
//     only does registerNatives and keeps the rest of the class
//     initialization work later until thread initialization completes.
//
//     System.initPhase1 initializes the system properties, the static
//     fields in, out, and err. Set up java signal handlers, OS-specific
//     system settings, and thread group of the main thread.
static void call_initPhase1(TRAPS) {
  Klass* klass = vmClasses::System_klass();
  JavaValue result(T_VOID);
  JavaCalls::call_static(&result, klass, vmSymbols::initPhase1_name(),
                                         vmSymbols::void_method_signature(), CHECK);
}

// This will initialize the module system.  Only java.base classes can be
  // loaded until phase 2 completes
  call_initPhase2(CHECK_JNI_ERR);
// Phase 2. Module system initialization
//     This will initialize the module system.  Only java.base classes
//     can be loaded until phase 2 completes.
//
//     Call System.initPhase2 after the compiler initialization and jsr292
//     classes get initialized because module initialization runs a lot of java
//     code, that for performance reasons, should be compiled.  Also, this will
//     enable the startup code to use lambda and other language features in this
//     phase and onward.
//
//     After phase 2, The VM will begin search classes from -Xbootclasspath/a.
static void call_initPhase2(TRAPS) {
  TraceTime timer("Initialize module system", TRACETIME_LOG(Info, startuptime));

  Klass* klass = vmClasses::System_klass();

  JavaValue result(T_INT);
  JavaCallArguments args;
  args.push_int(DisplayVMOutputToStderr);
  args.push_int(log_is_enabled(Debug, init)); // print stack trace if exception thrown
  JavaCalls::call_static(&result, klass, vmSymbols::initPhase2_name(),
                                         vmSymbols::boolean_boolean_int_signature(), &args, CHECK);
  if (result.get_jint() != JNI_OK) {
    vm_exit_during_initialization(); // no message or exception
  }

  universe_post_module_init();
}


// Final system initialization including security manager and system class loader
  call_initPhase3(CHECK_JNI_ERR);

// Phase 3. final setup - set security manager, system class loader and TCCL
//
//     This will instantiate and set the security manager, set the system class
//     loader as well as the thread context class loader.  The security manager
//     and system class loader may be a custom class loaded from -Xbootclasspath/a,
//     other modules or the application's classpath.
static void call_initPhase3(TRAPS) {
  Klass* klass = vmClasses::System_klass();
  JavaValue result(T_VOID);
  
  //这行代码调用了一个静态方法。
  //call_static方法的参数包括:
  //1. 结果存储对象的地址
  //2. 要调用的静态方法所在的类:System.class
  //3. 要调用的方法的名字initPhase3, 
  //   在jdk-master/src/hotspot/share/classfile/vmSymbols.hpp定义
  //4. 要调用的方法的签名
  //5. 异常处理对象
  //这里调用的是System类的initPhase3方法,该方法没有参数并且返回类型为void
  JavaCalls::call_static(&result, klass, vmSymbols::initPhase3_name(),
                                         vmSymbols::void_method_signature(), CHECK);
}

//这几个阶段,都和System.java有关,具体的初始化过程参见System.java.

System.java - InitPhase1,2,3

源码位置:/jdk-master/src/java.base/share/classes/java/lang/System.java
System_Init1_2_3

Pharse1 - 第一阶中几个关键点

  • 设置系统编码(jnu.encoding):检查并确保sun.jnu.encoding系统属性是支持的编码。如果不支持或没有设置,则设置为默认值"UTF-8"。
  • 静态属性加载:加载某些静态属性,比如java.home。
  • 设置行分隔符属性: (line.separator)。
  • 初始化标准输入输出流:创建FileInputStream和FileOutputStream流,并且为标准输入输出创建缓冲流。

Pharse2 - 初始化模块系统

这里提一个小知识点,模块系统是Java 9中引入 的一个重要我发,它的目的是提供更好的封装性和模块化能力,从而改进大型应用和库的构建、维护和部署。

Pharse3 - 系统初始化最后一阶段

  • 初始化引导方法工厂
  • 设置安全管理器
  • 设置系统类加载器
  • 设置线程上下文类加载器

这里我们需要重点关注:设置系统类加载器

初始化ClassLoader
在这个过程中Java的几种类加载器都将会被始化,我们无法主动控制这些类加载器的创建,但我们可以定义自己的类加载器。以下是Java中的几种类加载器:

  • 引导类加载器(Bootstrap ClassLoader):

    • 这是由JVM实现的,用C++编写的。
    • 它负责加载JVM的核心类库,例如rt.jar或者java.*包中的类。
    • 由于是由JVM的原生代码实现,它并不是继承自java.lang.ClassLoader,通常在Java程序中不能直接引用引导类加载器。
  • 系统类加载器(System/Application ClassLoader):

    • 这是ClassLoader层次中的第三层,它负责加载环境变量CLASSPATH或者系统属性java.class.path指定路径中的类库,通常用来加载我们编写的类和第三方库。
    • 它是sun.misc.Launcher内部类AppClassLoader(在JDK9之前)或jdk.internal.loader.ClassLoaders内部类AppClassLoader(JDK9及之后)的实例。
  • 用户自定义类加载器:

    • 开发者可以通过继承ClassLoader类来实现自定义的类加载器。
    • 通过定义自己的加载策略,可以完成例如从网络加载类,解密类文件等特殊的加载需求。

JDK 9中引入模块系统后,引入新的类加载器:

  • 平台类加载器(Platform ClassLoader):
    • 在模块系统中取代了传统的扩展类加载器。
    • 它负责加载提供平台特定功能(如特定于JVM实现的部分)的模块。

总结

文章到这里,基本上已经将JVM启动的过程的核心步骤描述清楚了。本文是基于OpenJDK 22版本的源码进行走读,大家可从Github上下载最新代码。
下载地址:

或者

#可以通过git clone的方式下载代码
#由于网络原因,我下载总是失败,最后直接选择下载了zip文件。
git clone https://github.com/openjdk/jdk.git

JVM的初始化过程是一个稳式的过程,当我们在进行Java应用调试的时候,这一过程我们无法看到,学习这一个过程,有助于我们理解类似于类的加载,对象的创建,初始化等过程。

关注我的公众号

欢迎大家关注我的公众号,一起交流软件开发、架构设计、云原生技术
TXZQ聊IT技术与架构

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值