JVM初始化简介
前言 JVM初始化简介 1\. 初始化 JVM (InitializeJVM) 2\. 加载主类 (Load Main Class) 3\. 寻找并验证 main 方法 4\. 调用 main 方法 5\. JavaMain函数核心代码 启动引导类加载器 (Bootstrap ClassLoader)源码分析 `LoadMainClass()`函数源码分析 补充说明 JVM_FindClassFromBootLoader源码分析 java启动过程主要源码
前言
本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容可能存在疏漏,恳请读者不吝指正。
JVM初始化简介
在JVM启动过程时,会在JVMInit()函数中进行JVM初始化,并创建创建一个新的线程来执行java应用的启动过程,就是continuation所指向的JavaMain()函数里面的逻辑。java.c中的JavaMain()函数的核心四个步骤如下:
1. 初始化 JVM (InitializeJVM)
这是最重头戏的部分。它会调用 JNI_CreateJavaVM,这个调用会:
分配堆内存:根据你设置的
-Xms和-Xmx划分内存。启动引导类加载器 (Bootstrap ClassLoader):加载
rt.jar或 Java 9 之后的模块化核心类。启动关键系统线程:如垃圾回收线程 (GC threads)、信号转发线程 (Signal Dispatcher) 和编译线程 (JIT Compiler)。
2. 加载主类 (Load Main Class)
JVM 启动后,它需要知道从哪里开始运行。
如果你运行的是
java MyClass,它会寻找MyClass.class。如果你运行的是
java -jar app.jar,它会读取MANIFEST.MF文件中的Main-Class属性。
3. 寻找并验证 main 方法
JVM 会在主类中寻找符合特定签名的入口: public static void main(String[] args) 如果找不到,或者签名不对(比如不是 static 的),程序就会在这里抛出经典的 Main method not found 错误并退出。
4. 调用 main 方法
最后,通过 JNI 调用 CallStaticVoidMethod。 至此,控制权正式从 C 语言的启动器移交给你的 Java 代码。
5. JavaMain函数核心代码
jdk\src\share\bin\java.c中 JavaMain()核心代码如下:
int JNICALL 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;// 省略部分代码if (!InitializeJVM(&vm, &env, &ifn)) {JLI_ReportErrorMessage(JVM_ERROR1);exit(1);}// 省略部分代码// 加载主类mainClass = LoadMainClass(env, mode, what);CHECK_EXCEPTION_NULL_LEAVE(mainClass);// 省略部分代码// 调用public static void main(String[] args)方法执行Java应用的逻辑mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");CHECK_EXCEPTION_NULL_LEAVE(mainID);(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;LEAVE();}
启动引导类加载器 (Bootstrap ClassLoader)源码分析
LoadMainClass()函数源码分析
java.c中 LoadMainClass()函数核心代码如下:
static jclass LoadMainClass(JNIEnv *env, int mode, char *name){jmethodID mid;jstring str;jobject result;jlong start, end;jclass cls = GetLauncherHelperClass(env);// 省略部分代码NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,"checkAndLoadMain","(ZILjava/lang/String;)Ljava/lang/Class;"));str = NewPlatformString(env, name);CHECK_JNI_RETURN_0(result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str));// 省略部分代码return (jclass)result;}
1、在GetLauncherHelperClass()函数中,最终通过JNI执行函数JVM_FindClassFromBootLoader()函数中查找到Java类sun.launcher.LauncherHelper。
java.c中 GetLauncherHelperClass()核心代码如下:
jclass GetLauncherHelperClass(JNIEnv *env){if (helperClass == NULL) {NULL_CHECK0(helperClass = FindBootStrapClass(env,"sun/launcher/LauncherHelper"));}return helperClass;}
jdk\src\solaris\bin\java_md_common.c中 FindBootStrapClass()代码。通过JNI调用libjvm.so中的JVM_FindClassFromBootLoader()。
jclass FindBootStrapClass(JNIEnv *env, constchar* classname){if (findBootClass == NULL) {findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,"JVM_FindClassFromBootLoader");if (findBootClass == NULL) {JLI_ReportErrorMessage(DLL_ERROR4,"JVM_FindClassFromBootLoader");return NULL;}}return findBootClass(env, classname);}
JVM_FindClassFromBootLoader源码参见补充说明部分章节JVM_FindClassFromBootLoader源码分析
2、在查找的Java类sun.launcher.LauncherHelper后,调用JNIEnv->CallStaticObjectMethod()函数,执行Java类LauncherHelper中checkAndLoadMain()方法。在该方法中调用java.lang.ClassLoader类中initSystemClassLoader()方法,创建ExtClassLoader和AppClassLoader对象并将AppClassLoader对象返回,后续使用AppClassLoader对象的loadClass()方法完成Java类的查找和加载。
jdk\src\share\classes\sun\launcher\LauncherHelper.java中方法 checkAndLoadMain()核心代码如下:
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();public static Class<?> checkAndLoadMain(boolean printToStderr,int mode,String what) {initOutput(printToStderr);// get the class nameString cn = null;switch (mode) {case LM_CLASS:cn = what;break;case LM_JAR:cn = getMainClassFromJar(what);break;default:// should never happenthrow new InternalError("" + mode + ": Unknown launch mode");}cn = cn.replace('/', '.');Class<?> mainClass = null;try {mainClass = scloader.loadClass(cn);} catch (NoClassDefFoundError | ClassNotFoundException cnfe) {// 省略部分代码}// 省略部分代码return mainClass;}
jdk\src\share\classes\java\lang\ClassLoader.java中方法 getSystemClassLoader() 该方法中会获取sun.misc.Launcher类中创建的ClassLoader(ExtClassLoader和AppClassLoader),其核心代码如下:
publicstatic ClassLoader getSystemClassLoader() {initSystemClassLoader();if (scl == null) {return null;}// 省略部分代码return scl;}privatestatic synchronized voidinitSystemClassLoader() {if (!sclSet) {// 省略部分代码sun.misc.Launcher l = sun.misc.Launcher.getLauncher();if (l != null) {scl = l.getClassLoader();}sclSet = true;}}
jdk\src\share\classes\sun\misc\Launcher.java,创建ExtClassLoader和AppClassLoader。
public class Launcher {// 省略部分代码private static Launcher launcher = new Launcher();publicstatic Launcher getLauncher() {return launcher;}private ClassLoader loader;publicLauncher() {// Create the extension class loaderClassLoader extcl;try {extcl = ExtClassLoader.getExtClassLoader();} catch (IOException e) {// 省略异常处理部分代码}// Now create the class loader to use to launch the applicationtry {loader = AppClassLoader.getAppClassLoader(extcl);} catch (IOException e) {// 省略异常处理部分代码}// Also set the context class loader for the primordial thread.Thread.currentThread().setContextClassLoader(loader);}public ClassLoader getClassLoader() {return loader;}}
补充说明
JVM_FindClassFromBootLoader源码分析
1、hotspot\src\share\vm\prims\jvm.cpp中 JVM_FindClassFromBootLoader()代码如下:
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name))JVMWrapper2("JVM_FindClassFromBootLoader %s", name);// Java libraries should ensure that name is never null...if(name == NULL || (int)strlen(name) > Symbol::max_length()) {// It's impossible to create this class; the name cannot fit// into the constant pool.return NULL;}TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);if(k == NULL) {return NULL;}if(TraceClassResolution) {trace_class_resolution(k);}return(jclass) JNIHandles::make_local(env, k->java_mirror());JVM_END
2、宏展开后的代码如下:
extern "C" {jclass JNICALL JVM_FindClassFromBootLoader(JNIEnv* env, constchar* name){JavaThread* thread=JavaThread::thread_from_jni_environment(env);ThreadInVMfromNative __tiv(thread);debug_only(VMNativeEntryWrapper __vew;);/* VM_ENTRY_BASE 展开内容 */TRACE_CALL(jclass, JVM_FindClassFromBootLoader)HandleMarkCleaner __hm(thread);Thread* THREAD = thread;os::verify_stack_alignment();/* 原始函数体开始 */JVMWrapper2("JVM_FindClassFromBootLoader %s", name);if (name == NULL || (int)strlen(name) > Symbol::max_length()) {return NULL;}TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);if (k == NULL) {return NULL;}if (TraceClassResolution) {trace_class_resolution(k);}return (jclass) JNIHandles::make_local(env, k->java_mirror());}} // extern "C" 闭合
3、hotspot\src\share\vm\runtime\thread.hpp中 **thread_from_jni_environment()**核心代码。通过JNIEnv获取其所属的JavaThread。
static JavaThread* thread_from_jni_environment(JNIEnv* env){JavaThread *thread_from_jni_env = (JavaThread*)((intptr_t)env - in_bytes(jni_environment_offset()));// Only return NULL if thread is off the thread list; starting to// exit should not return NULL.if (thread_from_jni_env->is_terminated()) {thread_from_jni_env->block_if_vm_exited();return NULL;} else {return thread_from_jni_env;}}static ByteSize jni_environment_offset(){return byte_offset_of(JavaThread, _jni_environment);}#define byte_offset_of(klass,field) in_ByteSize((int)offset_of(klass, field))
4、hotspot\src\share\vm\utilities\globalDefinitions_gcc.hpp核心代码。获取klass中field的偏移量,方便后续通过klass的首地址+偏移量方式获取field的内存地址。
inline ByteSize in_ByteSize(int size){ return size; }#define offset_of(klass,field) (size_t)((intx)&(((klass*)16)->field) - 16)
上述代码中 offset_of()作用:在编译时计算一个类(class)或结构体(struct)中某个成员变量(field)相对于对象起始地址的偏移量(Offset)。 代码((klass)16)->field)中使用16而不是0的原因如下:* 在标准的 C 语言 offsetof 宏中,通常使用 (klass*)0。但 OpenJDK 经常使用 16(或者其他非零值),原因如下:
规避空指针检测(NULL Pointer Detection): 有些编译器或静态检查工具在看到
(klass*)0并进行解引用(->field)时,会触发“对空指针解引用”的警告或错误,即使这只是在编译期计算地址。使用16可以巧妙地绕过这种检查。处理虚函数表(vtable)或特殊对齐: 在某些平台上,地址
0是受保护的,或者地址0到16之间可能存在特殊的对齐限制。选择16这种稍微偏移一点的地址,通常是为了确保地址计算在语义上是“安全”的。
5、hotspot\src\share\vm\runtime\thread.hpp
classJavaThread: publicThread{friend classVMStructs;private:JavaThread* _next; // The next thread in the Threads listoop _threadObj; // The Java level thread object// 省略部分代码JavaFrameAnchor _anchor; // Encapsulation of current java frame and it stateThreadFunction _entry_point;// _jni_environment变量定义JNIEnv _jni_environment;}
java启动过程主要源码
1、jdk\src\share\bin\java.h
#include"java.h"// 省略部分代码intJVMInit(InvocationFunctions* ifn, jlong threadStackSize,int argc, char **argv,int mode, char *what, int ret){// 省略部分代码// ContinueInNewThread是java.c中的方法return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);}
2、jdk\src\share\bin\java.h中ContinueInNewThread0()函数声明。
/** Block current thread and continue execution in new thread*/intContinueInNewThread0(int (JNICALL *continuation)(void *),jlong stack_size, void * args);
3、jdk\src\share\bin\java.c中方法 ContinueInNewThread()核心代码。JavaMain函数的地址,在ContinueInNewThread0()函数中作为参数传入pthread_create()函数,这样在创建一个新的线程后,当新线程被执行时就会JavaMain函数逻辑。
#include "java.h"// 省略部分代码intContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,int argc, char **argv,int mode, char *what, int ret){// 省略部分代码,仅仅保留核心代码{JavaMainArgs args;int rslt;args.argc = argc;args.argv = argv;args.mode = mode;args.what = what;args.ifn = *ifn;// java.h中定义的方法ContinueInNewThread0(),该方法用于阻塞当前线程并创建新的线程执行java应用程序的启动过程rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);return (ret != 0) ? ret : rslt;}}
4、jdk\src\solaris\bin\java_md_solinux.c中方法ContinueInNewThread0()核心代码。
intContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {int rslt;pthread_t tid;pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);if (stack_size > 0) {pthread_attr_setstacksize(&attr, stack_size);}if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {void * tmp;pthread_join(tid, &tmp);rslt = (int)tmp;}// 省略部分代码pthread_attr_destroy(&attr);// 省略部分代码return rslt;}
夜雨聆风