这里写目录标题
关于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的方法。
2. JLI_Launch 入口
源码位置:/jdk-master/src/java.base/share/native/libjli/java.c
它主要做的工作是解析命令行参数,设置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的初始化工作。
4. 开启新线程并继续
又回到了libjli中的java.c文件
源码位置:/jdk-master/src/java.base/share/native/libjli/java.c
5. 调用JavaMain
回到/jdk-master/src/java.base/unix/native/libjli/java_md.c文件,
- CallJavaMainInNewThread 方法实现
- 最底下调用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
Pharse1 - 第一阶中几个关键点
- 设置系统编码(jnu.encoding):检查并确保sun.jnu.encoding系统属性是支持的编码。如果不支持或没有设置,则设置为默认值"UTF-8"。
- 静态属性加载:加载某些静态属性,比如java.home。
- 设置行分隔符属性: (line.separator)。
- 初始化标准输入输出流:创建FileInputStream和FileOutputStream流,并且为标准输入输出创建缓冲流。
Pharse2 - 初始化模块系统
这里提一个小知识点,模块系统是Java 9中引入 的一个重要我发,它的目的是提供更好的封装性和模块化能力,从而改进大型应用和库的构建、维护和部署。
Pharse3 - 系统初始化最后一阶段
- 初始化引导方法工厂
- 设置安全管理器
- 设置系统类加载器
- 设置线程上下文类加载器
这里我们需要重点关注:设置系统类加载器
在这个过程中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应用调试的时候,这一过程我们无法看到,学习这一个过程,有助于我们理解类似于类的加载,对象的创建,初始化等过程。
关注我的公众号
欢迎大家关注我的公众号,一起交流软件开发、架构设计、云原生技术