当我们运行Java程序main方法的时候,我们都知道当前线程是main线程
1
| Thread.currentThread().getName()
|
那么这个main线程是被谁启动,又是在什么时候被启动的呢?我们通过源码一探究竟。
jvm的启动入口是main.c,由于我之前可以在mac上调试jvm了,所以我通过下面的参数进行启动
1
| java -Xss512K -XX:+UseConcMarkSweepGC -Xms512M Main arg1=think123 arg2=666
|
1 2 3 4 5 6 7 8 9 10 11
| public class Main {
public static void main(String[] args) {
for(String arg: args) { System.out.println("input arg : " + arg); }
System.out.println("main thread name : " + Thread.currentThread().getName()); } }
|
main.c中首先会通过启动器来创建启动jvm
1 2 3 4 5 6 7 8 9
| return JLI_Launch(margc, margv, jargc, (const char**) jargv, 0, NULL, VERSION_STRING, DOT_VERSION, (const_progname != NULL) ? const_progname : *margv, (const_launcher != NULL) ? const_launcher : *margv, jargc > 0, const_cpwildcard, const_javaw, 0);
|
JLI_Launch的实现在java.c文件中,它的主要流程是
创建执行环境,主要是确定jrepath/jvmpath
加载jvm
解析参数
初始化jvm,执行main方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| JNIEXPORT int JNICALL JLI_Launch(int argc, char ** argv, int jargc, const char** jargv, int appclassc, const char** appclassv, const char* fullversion, const char* dotversion, const char* pname, const char* lname, jboolean javaargs, jboolean cpwildcard, jboolean javaw, jint ergo ) { char jvmpath[MAXPATHLEN]; char jrepath[MAXPATHLEN]; char jvmcfg[MAXPATHLEN];
CreateExecutionEnvironment(&argc, &argv, jrepath, sizeof(jrepath), jvmpath, sizeof(jvmpath), jvmcfg, sizeof(jvmcfg));
if (!IsJavaArgs()) { SetJvmEnvironment(argc,argv); }
ifn.CreateJavaVM = 0; ifn.GetDefaultJavaVMInitArgs = 0;
if (!LoadJavaVM(jvmpath, &ifn)) { return(6); }
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath)) { return(ret); }
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret); }
|
上面的代码我保留了主体流程,将其他代码省略掉了。
创建执行环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| void CreateExecutionEnvironment(int *pargc, char ***pargv, char jrepath[], jint so_jrepath, char jvmpath[], jint so_jvmpath, char jvmcfg[], jint so_jvmcfg) {
jboolean jvmpathExists;
SetExecname(*pargv);
char * jvmtype = NULL; int argc = *pargc; char **argv = *pargv;
if (!GetJREPath(jrepath, so_jrepath, JNI_FALSE) ) { JLI_ReportErrorMessage(JRE_ERROR1); exit(2); }
if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath)) { JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath); exit(4); }
MacOSXStartup(argc, argv);
return; }
|
需要注意的是jvmpath/jrepath的长度不能超过1024字节,所以我们安装java的时候一定要注意文件夹层次不能太深
执行完上面的代码之后,jvmpath/jrepath的值如下
着重注意下这里的jvmpath的值是libjvm.dylib,这个就是我们要使用的JVM动态链接库(windows中是jvm.dll,linux中是libjvm.so)
加载JVM
接下来加载JVM,实际上是加载libjvm.dylib这个动态链接库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| boolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn) { Dl_info dlinfo; void *libjvm;
#ifndef STATIC_BUILD libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL); #else libjvm = dlopen(NULL, RTLD_FIRST); #endif if (libjvm == NULL) { JLI_ReportErrorMessage(DLL_ERROR1, __LINE__); JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror()); return JNI_FALSE; }
ifn->CreateJavaVM = (CreateJavaVM_t) dlsym(libjvm, "JNI_CreateJavaVM");
if (ifn->CreateJavaVM == NULL) { JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror()); return JNI_FALSE; } ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t) dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
if (ifn->GetDefaultJavaVMInitArgs == NULL) { JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror()); return JNI_FALSE; }
ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t) dlsym(libjvm, "JNI_GetCreatedJavaVMs");
if (ifn->GetCreatedJavaVMs == NULL) { JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror()); return JNI_FALSE; }
return JNI_TRUE; }
|
LoadJVM主要做了以下2件事
- 通过dlopen加载libjvm.dylib动态链接库
- 绑定动态链接库中的函数到InvocationFunctions这个结构体的属性中
dlopen和dlsym系统提供的函数,dlsym一般和dlopen配合使用
解析命令行参数
在ParseArguments函数中主要解析的是命令行参数比如-classpath,-version,-help等,但是这里最重要的是解析-Xss,-Xmx,-Xms这三个参数,因为这三个参数格式和其他不一样。都是参数名称后面跟上具体大小
单位只能是T(t),G(g),M(m),K(k)这8个中的一个
其他参数解析和判定会在初始化JVM的时候完成
初始化VM
JVMInit方法最终会调用java_md_macosx.m中的ContinueInNewThread0方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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); } pthread_attr_setguardsize(&attr, 0);
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) { void * tmp; pthread_join(tid, &tmp); rslt = (int)(intptr_t)tmp; } else { rslt = continuation(args); }
pthread_attr_destroy(&attr); return rslt; }
|
pthread_create函数作用是创建一个线程(我们的main线程就这样被创建出出来了),pthread_create的第三个参数continuation传递进来的函数是JavaMain,这就相当于java线程中run方法。它位于java.c中,由于函数过长,我只保留了比较重要的部分
IEEE标准1003.1c中定义了线程的标准,它定义的线程包叫做Pthread,大部分UNIX系统都支持这个标准。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| int JNICALL JavaMain(void * _args) { ... 省略代码 if (!InitializeJVM(&vm, &env, &ifn)) { JLI_ReportErrorMessage(JVM_ERROR1); exit(1); }
ret = 1;
mainClass = LoadMainClass(env, mode, what); CHECK_EXCEPTION_NULL_LEAVE(mainClass); mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
LEAVE(); }
|
调用main方法则是通过jni.cpp中的jni_invoke_static方法,而该方法最终是通过JavaCalls::call(javaCalls.cpp)完成的。
javaCalls::call方法只有java线程才能调用该方法
至此我们的main线程就被启动起来了。