Java 程序启动


你们说,带群友翻 jdk 代码会不会很酷?

拜托,超酷的好不好。

假设

首先假设在工作目录中指定并启动编译好的主类:

$ java MainClass

至于 jar 包,你用压缩软件打开,然后打开文件 META-INF/MANIFEST.MF,里面 Main-Class 那一行就写着主类:

Main-Class: xxx

入口点

入口点不难找到,就在 src/java.base/share/native/launcher/main.c。 里面根据 JAVAW 宏控制使用 C 语言入口 main() 还是 Windows 窗体应用程序入口 WinMain()。 接着进入 JLI_Launch()

Java 参数

JLI_Launch()src/java.base/share/native/libjli/java.c 中。

有意思的是,这个函数会调用 ParseArguments() 函数,在这个函数里面就会解析我们传入的主类名字。 通过指针参数向外写值。其中 mode 代表主类模式,是编译好的主类,源文件,jar 还是模块,等。 what 是主类/jar 包/主模块的名字。

其中:

} else if (mode == LM_UNKNOWN) {
    /* default to LM_CLASS if -m, -jar and -cp options are
     * not specified */
    if (!_have_classpath) {
        SetClassPath(".");
    }
    mode = IsSourceFile(arg) ? LM_SOURCE : LM_CLASS;
} else if ...

处理上述情况,根据传入的名字是源文件还是编译后的类决定 mode 的值。 并且将 ClassPath 设置为当前工作目录。

继续

接着进入 JVMInit()

JVMInit() 平台相关,就先看 unix 版本,在 src/java.base/unix/native/libjli/java_md.c 中。 显示启动画面,然后进入 ContinueInNewThread()ContinueInNewThread() 再进入 CallJavaMainInNewThread()

unix 版本的在 src/java.base/unix/native/libjli/java_md.c 中。

static void* ThreadJavaMain(void* args) {
    return (void*)(intptr_t)JavaMain(args);
}

int
CallJavaMainInNewThread(jlong stack_size, void* args) {
    ...
    if (pthread_create(&tid, &attr, ThreadJavaMain, args) == 0) {
        void* tmp;
        pthread_join(tid, &tmp);
        rslt = (int)(intptr_t)tmp;
    } else {
       /*
        * Continue execution in current thread if for some reason (e.g. out of
        * memory/LWP)  a new thread can't be created. This will likely fail
        * later in JavaMain as JNI_CreateJavaVM needs to create quite a
        * few new threads, anyway, just give it a try..
        */
        rslt = JavaMain(args);
    }
    ...
}

从代码中可以看出都会进入 JavaMain() 函数。

JavaMain

JavaMain 中的代码就非常有意思了。在 src/java.base/share/native/libjli/java.c

mainClass = LoadMainClass(env, mode, what);

这里加载主类,env 相当于 JVM 的接口,mode 表示主类的类型,what 表示主类的名字。

最后就是获取并调用 main 方法。

// 获取 main 方法
mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");

// 调用 main方法
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

加载主类

同一个文件,LoadMainClass() 中是对 java 程序的 checkAndLoadMain 的调用:

mid = (*env)->GetStaticMethodID(env, cls, "checkAndLoadMain", "(ZILjava/lang/String;)Ljava/lang/Class;");
(*env)->CallStaticObjectMethod(env, cls, mid, USER_STDERR, mode, str);

checkAndLoadMainsrc/java.base/share/classes/sun/launcher/LauncherHelper.java 中。 根据 mode 的值,对于我们的情况会调用 loadMainClass()

cn = what; // case LM_CLASS 我们的情况
Class<?> mainClass = null;
ClassLoader scl = ClassLoader.getSystemClassLoader();
try{
    try{
        mainClass = Class.forName(cn, false, scl); // 加载主类
    }
}
return mainClass;

ClassLoader

这个 getSystemClassLoader() 估计会返回 AppClassLoader。 在 src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java

URLClassPath ucp = new URLClassPath(append, true);
BOOT_LOADER = new BootClassLoader(ucp);
PLATFORM_LOADER = new PlatformClassLoader(BOOT_LOADER);
APP_LOADER = new AppClassLoader(PLATFORM_LOADER, ucp);

对于 ClassLoader,最终找到的是一个 native 声明:

static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain, pd, String source);

其实现在 src/java.base/share/native/libjava/ClassLoader.c

JNIEXPORT jclass JNICALL
java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
                                        Jclass cls,
                                        jobject loader,
                                        jstring name,
                                        jbyteArray data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)

经过处理,进入 JVM_DefineClassWithSource() 定义在 src/hotspot/share/prims/jvm.cpp

Class.forName

Class.forNamesrc/java.base/share/classes/java/lang/Class.java,最终会进入 forName0

private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader,
                                        Class<?> caller)
    throws ClassNotFoundException;

既然是 native 方法,那么实现就在 src/java.base/share/native/libjava/Class.c,叫 Java_java_lang_Class_forName0。 经过处理,传递给 JVM_FindClassFromCaller

JVM_FindClassFromCallersrc/hotspot/share/prims/jvm.cpp中,传递给 find_class_from_class_loader。 再传入 SystemDictionary::resolve_or_fail

SystemDictionary::resolve_or_failsrc/hotspot/share/classfile/systemDictionary.cpp 中,传入 resolve_or_nullresolve_instance_class_or_null_helperresolve_instance_class_or_null

resolve_instance_class_or_null 中有个调用 load_instance_class,在进入 load_instance_class_impl

调用 main 方法

CallStaticVoidMethod 会被转发为 CallStaticVoidMethodV,接着对应 src/hotspot/share/prims/jni.cpp 的这个:

JNI_ENTRY(void, jni_CallStaticVoidMethodV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args))
  HOTSPOT_JNI_CALLSTATICVOIDMETHODV_ENTRY(env, cls, (uintptr_t) methodID);
  DT_VOID_RETURN_MARK(CallStaticVoidMethodV);

  JavaValue jvalue(T_VOID);
  JNI_ArgumentPusherVaArg ap(methodID, args);
  jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);
JNI_END

不难发现,进入 jni_invoke_static

JavaCalls::call(result, method, &java_args, CHECK);

JavaCalls::callsrc/hotspot/share/runtime/javaCalls.cpp 中,代码比较有意思:

void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
  // Check if we need to wrap a potential OS exception handler around thread.
  // This is used for e.g. Win32 structured exception handlers.
  // Need to wrap each and every time, since there might be native code down the
  // stack that has installed its own exception handlers.
  os::os_exception_wrapper(call_helper, result, method, args, THREAD);
}

不难发现,相当于转发给了下面的 call_helper。 其中可以找到:

StubRoutines::call_stub()(
    (address)&link,
    // (intptr_t*)&(result->_value),
    result_val_address,
    result_type,
    method(),
    entry_point,
    parameter_address,
    args->size_of_parameters(),
    CHECK
);

这个 StubRoutines::call_stub()src/hotspot/share/runtime/stubRoutines.hpp 中:

class StubRoutines: AllStatic {
    ...
    static address _call_stub_entry;
    ...
    static CallStub call_stub() { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }
    ...
}

其中 CAST_TO_FN_PTR 是强制类型转换的宏,重要的是 _call_stub_entry。 这个成员的初始化是实现相关的,以 x86_64 为例,初始化的代码在 src/hotspot/cpu/x86/stubGenerator_x86_64.cpp 中的 generate_initial()

StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address);

函数 generate_call_stub 就在同一个文件,内部的代码估计就是汇编代码生成了。