乐于分享
好东西不私藏

Java源码探秘:JVM启动与类加载机制

Java源码探秘:JVM启动与类加载机制

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类LauncherHelpercheckAndLoadMain()方法。在该方法中调用java.lang.ClassLoader类中initSystemClassLoader()方法,创建ExtClassLoaderAppClassLoader对象并将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 name        String cn = null;        switch (mode) {            case LM_CLASS:                cn = what;                break;            case LM_JAR:                cn = getMainClassFromJar(what);                break;            default:                // should never happen                throw 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(ExtClassLoaderAppClassLoader),其核心代码如下:

    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,创建ExtClassLoaderAppClassLoader

public class Launcher {    // 省略部分代码    private static Launcher launcher = new Launcher();    publicstatic Launcher getLauncher() {        return launcher;    }    private ClassLoader loader;    publicLauncher() {        // Create the extension class loader        ClassLoader extcl;        try {            extcl = ExtClassLoader.getExtClassLoader();        } catch (IOException e) {            // 省略异常处理部分代码        }        // Now create the class loader to use to launch the application        try {            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

classJavaThreadpublicThread{  friend classVMStructs; private:  JavaThread*    _next;                          // The next thread in the Threads list  oop            _threadObj;                     // The Java level thread object// 省略部分代码  JavaFrameAnchor _anchor;                       // Encapsulation of current java frame and it state  ThreadFunction _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;}