这里介绍的是jdk8u的源码,系统环境是Linux version 4.15.0-142-generic (buildd@lgw01-amd64-039) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)) #146~16.04.1-Ubuntu SMP Tue Apr 13 09:27:15 UTC 2021
启动入口函数
jvm启动的入口函数在jdk/src/share/bin目录下main.c文件中,我们这里是64位操作系统,调试的文件是普通带有main方法的Java的class文件,main.c在中的主方法
int main(int argc, char **argv)
{
int margc;
char** margv;
const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32 //这里是64位系统直接跳过到创建JLI_Launch
{
int i = 0;
if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
printf("Windows original main args:\n");
for (i = 0 ; i < __argc ; i++) {
printf("wwwd_args[%d] = %s\n", i, __argv[i]);
}
}
}
JLI_CmdToArgs(GetCommandLine());
margc = JLI_GetStdArgc();
// add one more to mark the end
margv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *)));
{
int i = 0;
StdArg *stdargs = JLI_GetStdArgs();
for (i = 0 ; i < margc ; i++) {
margv[i] = stdargs[i].arg;
}
margv[i] = NULL;
}
#else /* *NIXES */
margc = argc;//参数个数
margv = argv;//参数指针
#endif /* WIN32 */
return JLI_Launch(margc, margv,
sizeof(const_jargs) / sizeof(char *), const_jargs,
sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
FULL_VERSION,
DOT_VERSION,
(const_progname != NULL) ? const_progname : *margv,
(const_launcher != NULL) ? const_launcher : *margv,
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
const_cpwildcard, const_javaw, const_ergo_class);
}
调试主要执行了一个赋值和创建引导器JLI_Launch,这里的参数个数是4个,分别是编译好的java可执行文件,cp类路径,编译好的class文件
加载libjvm.so动态库文件过程
继续debug查看库文件的加载过程.java.c中的启动方法
int
JLI_Launch(int argc, char ** argv, /* main argc, argc */
int jargc, const char** jargv, /* java args */
int appclassc, const char** appclassv, /* app classpath */
const char* fullversion, /* full version defined */
const char* dotversion, /* dot version defined */
const char* pname, /* program name */
const char* lname, /* launcher name */
jboolean javaargs, /* JAVA_ARGS */
jboolean cpwildcard, /* classpath wildcard*/
jboolean javaw, /* windows-only javaw */
jint ergo /* ergonomics class policy */
)
{
int mode = LM_UNKNOWN;
char *what = NULL;
char *cpath = 0;
char *main_class = NULL;
int ret;
InvocationFunctions ifn;
jlong start = 0, end = 0;
char jvmpath[MAXPATHLEN];
char jrepath[MAXPATHLEN];
char jvmcfg[MAXPATHLEN];
_fVersion = fullversion;//1.8.0_352-internal-debug-root_2023_03_29_04_00-b00
_dVersion = dotversion;//1.8
_launcher_name = lname;//openjdk
_program_name = pname;//java
_is_java_args = javaargs;//0
_wc_enabled = cpwildcard;//1
_ergo_policy = ergo;//0
//初始化引导器,这里javaw是0,没有main函数的应用创建启动类引导器
InitLauncher(javaw);
DumpState();//打印参数信息
if (JLI_IsTraceLauncher()) {
int i;
printf("Command line args:\n");
for (i = 0; i < argc ; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
AddOption("-Dsun.java.launcher.diag=true", NULL);
}
/*
* Make sure the specified version of the JRE is running.
*
* There are three things to note about the SelectVersion() routine:
* 1) If the version running isn't correct, this routine doesn't
* return (either the correct version has been exec'd or an error
* was issued).
* 2) Argc and Argv in this scope are *not* altered by this routine.
* It is the responsibility of subsequent code to ignore the
* arguments handled by this routine.
* 3) As a side-effect, the variable "main_class" is guaranteed to
* be set (if it should ever be set). This isn't exactly the
* poster child for structured programming, but it is a small
* price to pay for not processing a jar file operand twice.
* (Note: This side effect has been disabled. See comment on
* bugid 5030265 below.)
*/
SelectVersion(argc, argv, &main_class);//根据版本设置相应参数
/** **/
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
if (!IsJavaArgs()) {
SetJvmEnvironment(argc,argv);//设置jvm的环境变量,比如一些jvm调优参数
}
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
/*这里通过调用系统函数dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL)加载文件libjvm.so,
* 通过 void *dlsym(void *handle, const char *symbol)拿到handle函数的地址(函数指针)
* ifn->CreateJavaVM = (CreateJavaVM_t) dlsym(libjvm, "JNI_CreateJavaVM");
* 这里得jvmpath就是openjdk源码编译生成的库文件地址,在源码编译后的build文件夹中
*/
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
if (JLI_IsTraceLauncher()) {
end = CounterGet();
}
JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
(long)(jint)Counter2Micros(end-start));
++argv;
--argc;
if (IsJavaArgs()) {
/* Preprocess wrapper arguments */
TranslateApplicationArgs(jargc, jargv, &argc, &argv);
if (!AddApplicationOptions(appclassc, appclassv)) {
return(1);
}
} else {
/* Set default CLASSPATH */
cpath = getenv("CLASSPATH");
if (cpath == NULL) {
cpath = ".";
}
SetClassPath(cpath);
}
/* Parse command line options; if the return value of
* ParseArguments is false, the program should exit.
*/
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
{
return(ret);
}
/* Override class path if -jar flag was specified */
if (mode == LM_JAR) {
SetClassPath(what); /* Override class path */
}
/* set the -Dsun.java.command pseudo property */
SetJavaCommandLineProp(what, argc, argv);
/* Set the -Dsun.java.launcher pseudo property */
SetJavaLauncherProp();
/* set the -Dsun.java.launcher.* platform properties */
SetJavaLauncherPlatformProps();
/** 这里继续调用java_md_slinux.c文件 JVMInit方法-》Java.c的ContinueInNewThread,
* 最后还是调用java_md_slinux.c文件的ContinueInNewThread0方法,
* 在这里创建java线程,并继续在新创建的Java线程里执行javaMain方法
*/
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
主要拿到的函数是JNI_CreateJavaVM,JNI_GetDefaultJavaVMInitArgs,JNI_GetCreatedJavaVMs,拿到动态库中的这三个函数后继续jvm的初始化,接下来就是参数的解析赋值,创建vm线程,编译线程等,java_md_solinux.c
int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
int rslt;
#ifndef __solaris__
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);
}
/**在这里系统调用创建线程函数,在新的线程中继续执行函数,continuation就是JavaMain函数**/
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp);
rslt = (int)tmp;
} else {
/*
* 如果由于某些原因(例如内存不足/LWP)无法创建新线程,则继续在当前线程中执行。这很可能会失败
* 因为JNI_CreateJavaVM需要创建相当多的新线程,无论如何,请尝试一下
*/
rslt = continuation(args);
}
pthread_attr_destroy(&attr);
#else /* __solaris__ */
thread_t tid;
long flags = 0;
if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
void * tmp;
thr_join(tid, NULL, &tmp);
rslt = (int)tmp;
} else {
/* See above. Continue in current thread if thr_create() failed */
rslt = continuation(args);
}
#endif /* !__solaris__ */
return rslt;
}
java虚拟机的创建
JavaMain函数中就开始初始化并且创建jvm相关线程,有VM线程,C1编译线程,C2编译线程,service线程,java.c
源码部分
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;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start = 0, end = 0;
RegisterThread();
start = CounterGet();
/* 这里正式初始化创建jvm */
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
if (showSettings != NULL) {
ShowSettings(env, showSettings);
CHECK_EXCEPTION_LEAVE(1);
}
if (printVersion || showVersion) {
PrintJavaVersion(env, showVersion);
CHECK_EXCEPTION_LEAVE(0);
if (printVersion) {
LEAVE();
}
}
/* If the user specified neither a class name nor a JAR file */
if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
PrintUsage(env, printXUsage);
CHECK_EXCEPTION_LEAVE(1);
LEAVE();
}
FreeKnownVMs(); /* after last possible PrintUsage() */
if (JLI_IsTraceLauncher()) {
end = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
(long)(jint)Counter2Micros(end-start));
}
/* At this stage, argc/argv have the application's arguments */
if (JLI_IsTraceLauncher()){
int i;
printf("%s is '%s'\n", launchModeNames[mode], what);
printf("App's argc is %d\n", argc);
for (i=0; i < argc; i++) {
printf(" argv[%2d] = '%s'\n", i, argv[i]);
}
}
ret = 1;
/*
* 获取应用的主类main class
*
* Main-Class名称已经从清单中解析出来了,但是没有正确解析出UTF-8支持。
* 因此,这里的代码忽略了先前提取的值,并使用先前存在的代码重新提取该值。
* 这可能是结束发布周期的权宜之计。然而,也发现通过环境传递一些字符集在某些
* Windows变体上有“奇怪”的行为。因此,可能永远不应该增强启动器本地的清单解析代码。
*
* 因此,接下来的操作是
* 1)纠正本地解析代码,并验证Main-Class属性在所有环境中正确传递。
* 2)删除通过环境维护main_class的遗留问题(并删除这些注释)。该方法还可以正确地处理启动现有的JavaFX应用程序,这些应用程序可能有也可能没有Main-Class清单条目。
*/
mainClass = LoadMainClass(env, mode, what);/* */
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/*
* 在某些场景下,当启动一个应用程序的时候需要一个帮助类启动器,例如
* 没有main方法的JavaFX应用程序,这类应用程序的main-class就是帮助类启动器里面的主类
* 为了保证在UI上保持一致所以需要追踪和报告应用程序的main-class
*/
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1);
/*
* PostJVMInit使用类名作为GUI用途的应用程序名称,例如,在OSX上
* 它在菜单栏中为SWT和JavaFX设置应用程序名称。我们在这里传递实际
* 的应用程序类,而不是mainClass,因为那可能是一个启动器或助手类
* 而不是应用程序类。
*/
PostJVMInit(env, appClass, vm);
CHECK_EXCEPTION_LEAVE(1);
/*
* LoadMainClass 不仅加载主类,还需要确保主方法的签名是正确的,
* 因此将不需要进一步检查。这里调用main方法,以便在应用程序堆栈跟踪中
* 不存在无关的java堆栈
*/
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* 参数平台相关化 */
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* 调用main方法 */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
/*
* 推出引导器System.exit,如果是非正常推出ret != 0
*/
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();
}
下面我们接着来看InitializeJVM(&vm, &env, &ifn)做了什么,调用libjvm.so动态库中的函数
这个方法的实现是再jni.cpp中,这里会看到xchg这个命令,这个就是java中atomic变量在底层硬件层面的指令实行,这里对jvm的创建同步控制就使用了这个,jni.cpp的主要方法是这个
_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
#ifndef USDT2
HS_DTRACE_PROBE3(hotspot_jni, CreateJavaVM__entry, vm, penv, args);
#else /* USDT2 */
HOTSPOT_JNI_CREATEJAVAVM_ENTRY(
(void **) vm, penv, args);
#endif /* USDT2 */
jint result = JNI_ERR;
DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result);
//这里将要使用 Atomic::xchg 命令进行同步,某些 Zero
// 平台使用 GCC builtin __sync_lock_test_and_set 来实现同步,
// 但是 __sync_lock_test_and_set 并不保证在所有的架构上执行的结果如我们预期的那样
// 所以在使用前需要先进行检测
#if defined(ZERO) && defined(ASSERT)
{
jint a = 0xcafebabe;
jint b = Atomic::xchg(0xdeadbeef, &a);
void *c = &a;
void *d = Atomic::xchg_ptr(&b, &c);
assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works");
assert(c == &b && d == &a, "Atomic::xchg_ptr() works");
}
#endif // ZERO && ASSERT
//因为一些运行时状态在全局变量中
//所以某一时刻只能有一个Java虚拟机
//不能使用mutex locks,因为他们只对线程有效
//使用原子的比较和交换以保证某一时刻只有一个线程调用这个方法
//这里使用 Atomic::xchg而不是用Atomic::add/dec
//因为在某些平台上add/dec的实现依赖我们的程序是否是跑在多核处理器上和初始阶段
//os::is_MP函数的返回值为false,而 Atomic::xchg则没有这个问题
if (Atomic::xchg(1, &vm_created) == 1) {
return JNI_EEXIST; // already created, or create attempt in progress
}
if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
return JNI_ERR; // someone tried and failed and retry not allowed.
}
assert(vm_created == 1, "vm_created is true during the creation");
/**初始化期间的某些错误是可以恢复的,并且不会阻止以后再次调用此方法(可能使用不同的参数)
* 然而,在初始化过程中的某个时刻,如果发生错误,我们不能允许再次调用此函数(否则它将崩溃)
* 在这些情况下,“canTryAgain”标志被设置为false,这将原子地将safe_to_recreate_vm设置为1,
* 这样使用上述逻辑对JNI_CreateJavaVM的任何新调用都将立即失败
*/
bool can_try_again = true;
/*调用thread.cpp创建jvm虚拟机*/
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
if (result == JNI_OK) {
JavaThread *thread = JavaThread::current();
/* thread is thread_in_vm here */
*vm = (JavaVM *)(&main_vm);
*(JNIEnv**)penv = thread->jni_environment();
// Tracks the time application was running before GC
RuntimeService::record_application_start();
// Notify JVMTI
if (JvmtiExport::should_post_thread_life()) {
JvmtiExport::post_thread_start(thread);
}
post_thread_start_event(thread);
#ifndef PRODUCT
#ifndef CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED
#define CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(f) f()
#endif
// Check if we should compile all classes on bootclasspath
if (CompileTheWorld) ClassLoader::compile_the_world();
if (ReplayCompiles) ciReplay::replay(thread);
// Some platforms (like Win*) need a wrapper around these test
// functions in order to properly handle error conditions.
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
#endif
// Since this is not a JVM_ENTRY we have to set the thread state manually before leaving.
ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
} else {
if (can_try_again) {
// reset safe_to_recreate_vm to 1 so that retrial would be possible
safe_to_recreate_vm = 1;
}
// Creation failed. We must reset vm_created
*vm = 0;
*(JNIEnv**)penv = 0;
// reset vm_created last to avoid race condition. Use OrderAccess to
// control both compiler and architectural-based reordering.
OrderAccess::release_store(&vm_created, 0);
}
return result;
}
继续debug查看在这里会去加载class文件,这里的what就是我的测试class文件Test
这就是jvm的启动过程了,下一期再深入到jvm类的加载过程中去看看