前言

本文旨在记录近期研读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, const char* 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),其核心代码如下:


    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        // 省略部分代码
        return scl;
    }

    private static synchronized void initSystemClassLoader() {
        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();

    public static Launcher getLauncher() {
        return launcher;
    }

    private ClassLoader loader;


    public Launcher() {
        // 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, const char* 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 是受保护的,或者地址 016 之间可能存在特殊的对齐限制。选择 16 这种稍微偏移一点的地址,通常是为了确保地址计算在语义上是“安全”的。

5、hotspot\src\share\vm\runtime\thread.hpp


class JavaThread: public Thread {
  friend class VMStructs;
 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"
// 省略部分代码

int JVMInit(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
 */
int ContinueInNewThread0(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"
// 省略部分代码

int ContinueInNewThread(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()核心代码。


int ContinueInNewThread0(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;
}

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐