java的入口位于jdk/src/share/bin/main.c
的main()
方法,下面是函数的实现:
#ifdef JAVAW
char **__initenv;
int WINAPI
WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow)
{
int margc;
char** margv;
const jboolean const_javaw = JNI_TRUE;
__initenv = _environ;
#else /* JAVAW */
int
main(int argc, char **argv)
{
int margc;
char** margv;
const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32
{
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);
}
上面的代码针对windows和linux环境定义了不同形式的入口,然后调用JLI_Launch
进行虚拟机的初始化以及启动。
JLI_Launch
我们先来看一下JLI_Launch的实现,
/*
* Entry point.
*/
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 */
)
{
......
/*
* 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.)
*/
// 选择jre版本
// 查找main_class
SelectVersion(argc, argv, &main_class);
// 解析jrepath, jvmpath, jvmcfg
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
......
// 加载虚拟机函数
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
......
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();
// 虚拟机初始化
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
上面的代码比较长,为了更好的理解我们将trace相关的代码省略,经过精简的代码大致分为下面的几个部分
- 选择jre版本
- 查找main_class
- 设置jrepath, jvmpath, jvmcfg
- 加载虚拟机函数
- CreateJavaVM_t CreateJavaVM; // 用于创建jvm
- GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
- GetCreatedJavaVMs_t GetCreatedJavaVMs;
- 解析参数
- 虚拟机初始化 JVMInit
选择jre版本
- 如果jar中的manifest中设置了
JRE-Version
,则设置jre版本 - 如果在命令行参数中设置了
-version:
,则覆盖manifest中的jre版本
查找main_class
- 如果设置了
_JAVA_VERSION_SET
,则从环境变量中提取main_class - 如果jar中的manifest中设置了
Main-Class
,则设置main_class
设置jrepath, jvmpath, jvmcfg
jrepath
static jboolean
GetJREPath(char *path, jint pathsize, const char * arch, jboolean speculative)
{
char libjava[MAXPATHLEN];
if (GetApplicationHome(path, pathsize)) {
/* Is JRE co-located with the application? */
JLI_Snprintf(libjava, sizeof(libjava), "%s/lib/%s/" JAVA_DLL, path, arch);
if (access(libjava, F_OK) == 0) {
JLI_TraceLauncher("JRE path is %s\n", path);
return JNI_TRUE;
}
/* Does the app ship a private JRE in <apphome>/jre directory? */
JLI_Snprintf(libjava, sizeof(libjava), "%s/jre/lib/%s/" JAVA_DLL, path, arch);
if (access(libjava, F_OK) == 0) {
JLI_StrCat(path, "/jre");
JLI_TraceLauncher("JRE path is %s\n", path);
return JNI_TRUE;
}
}
if (!speculative)
JLI_ReportErrorMessage(JRE_ERROR8 JAVA_DLL);
return JNI_FALSE;
}
jvmpath
jvmcfg
加载虚拟机函数
jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
void *libjvm;
JLI_TraceLauncher("JVM path is %s\n", jvmpath);
libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
if (libjvm == NULL) {
#if defined(__solaris__) && defined(__sparc) && !defined(_LP64) /* i.e. 32-bit sparc */
FILE * fp;
Elf32_Ehdr elf_head;
int count;
int location;
fp = fopen(jvmpath, "r");
if (fp == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/* read in elf header */
count = fread((void*)(&elf_head), sizeof(Elf32_Ehdr), 1, fp);
fclose(fp);
if (count < 1) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/*
* Check for running a server vm (compiled with -xarch=v8plus)
* on a stock v8 processor. In this case, the machine type in
* the elf header would not be included the architecture list
* provided by the isalist command, which is turn is gotten from
* sysinfo. This case cannot occur on 64-bit hardware and thus
* does not have to be checked for in binaries with an LP64 data
* model.
*/
if (elf_head.e_machine == EM_SPARC32PLUS) {
char buf[257]; /* recommended buffer size from sysinfo man
page */
long length;
char* location;
length = sysinfo(SI_ISALIST, buf, 257);
if (length > 0) {
location = JLI_StrStr(buf, "sparcv8plus ");
if (location == NULL) {
JLI_ReportErrorMessage(JVM_ERROR3);
return JNI_FALSE;
}
}
}
#endif
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;
}
上面的代码从jvm的library中加载三个函数。
- JNI_CreateJavaVM
- JNI_GetDefaultJavaVMInitArgs
- JNI_GetCreatedJavaVMs 加载过程首先通过dlopen打开library获取对应的句柄,然后通过dlsym从句柄指向的文件中加载函数。
解析参数
参数的解析是通过ParseArguments
完成的,这里有几个需要注意的地方,
-version,-no-jre-restrict-search,-jre-restrict-search,-splash
等几个已经处理过的参数需要忽略- 如果参数是
-Xss
,则设置threadStackSize
- 如果参数是
-Xms
,则设置initialHeapSize
- 如果参数是
-Xmx
,则设置maxHeadSize
JVMInit
int
JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
ShowSplashScreen();
return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}
JVMInit
调用ContinueInNewThread
。如果传入的threadStackSize
等于0,那么ContinueInNewThread
会调用GetDefaultJavaVMInitArgs
获取默认的threadStackSize
。在处理完threadStackSize
之后会调用ContinueInNewThread0
继续完成初始化。
int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
/*
* If user doesn't specify stack size, check if VM has a preference.
* Note that HotSpot no longer supports JNI_VERSION_1_1 but it will
* return its default stack size through the init args structure.
*/
if (threadStackSize == 0) {
struct JDK1_1InitArgs args1_1;
memset((void*)&args1_1, 0, sizeof(args1_1));
args1_1.version = JNI_VERSION_1_1;
ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */
if (args1_1.javaStackSize > 0) {
threadStackSize = args1_1.javaStackSize;
}
}
{ /* Create a new thread to create JVM and invoke main method */
JavaMainArgs args;
int rslt;
args.argc = argc;
args.argv = argv;
args.mode = mode;
args.what = what;
args.ifn = *ifn;
rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
/* If the caller has deemed there is an error we
* simply return that, otherwise we return the value of
* the callee
*/
return (ret != 0) ? ret : rslt;
}
}
需要注意的是传入到ContinueInNewThread0
中的方法是JavaMain
。
JavaMain
JavaMain
的代码比较长,为了更好的理解可以将其概述为下面的几步,
- 初始化虚拟机 – InitializeJVM
- 加载MainClass
- 获取main函数
- 调用main函数
虚拟机的初始化工作主要由InitializeJVM
完成,InitializeJVM
的实现相对简单,其主要负责调用ifn->CreateJavaVM(pvm, (void **)penv, &args)
。回想一下之前的内容,CreateJavaVM
是从jvm library加载的,并且存放在InvocationFunctions中。与之对应的真实的函数名为JNI_CreateJavaVM
,定义在jni.cpp中。
下面我们主要来看一下JNI_CreateJavaVM
的实现。
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);
#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
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");
bool can_try_again = true;
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);
}
EventThreadStart event;
if (event.should_commit()) {
event.set_javalangthread(java_lang_Thread::thread_id(thread->threadObj()));
event.commit();
}
#ifndef PRODUCT
#ifndef TARGET_OS_FAMILY_windows
#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;
}
代码的前一部分主要负责各种校验,如果校验失败则直接退出。在校验部分通过后,会调用Threads::create_vm((JavaVMInitArgs*) args, &can_try_again)
继续进行初始化操作。
create_vm的代码非常非常长,大致可以分为下面几步:
- 检查version
- 初始化输出流模块
- 处理启动properties
- os::init()
- 初始化系统properties
- jdk version初始化
- 根据jdk version更新系统properties
- 大页初始化
- os::init_2()
- ThreadLocalStorage初始化
- 初始化输出流日志
- convert_vm_init_libraries_to_agents
- create_vm_init_agents
- vm_init_globals
- synchronization子系统的初始化
- 进入多线程模式
- init_globals
- 加载java.lang.*
- 如果使用CMS或者G1则做相关操作
- 信号处理初始化
- AttachListener初始化
- 自旋锁初始化
- os::init_3()
这里需要重点介绍下步骤14和17.
vm_init_globals
void vm_init_globals() {
check_ThreadShadow();
basic_types_init();
eventlog_init();
mutex_init();
chunkpool_init();
perfMemory_init();
}
init_globals
jint init_globals() {
HandleMark hm;
management_init();
bytecodes_init();
classLoader_init();
codeCache_init();
VM_Version_init();
os_init_globals();
stubRoutines_init1();
jint status = universe_init(); // dependent on codeCache_init and
// stubRoutines_init1 and metaspace_init.
if (status != JNI_OK)
return status;
interpreter_init(); // before any methods loaded
invocationCounter_init(); // before any methods loaded
marksweep_init();
accessFlags_init();
templateTable_init();
InterfaceSupport_init();
SharedRuntime::generate_stubs();
universe2_init(); // dependent on codeCache_init and stubRoutines_init1
referenceProcessor_init();
jni_handles_init();
#if INCLUDE_VM_STRUCTS
vmStructs_init();
#endif // INCLUDE_VM_STRUCTS
vtableStubs_init();
InlineCacheBuffer_init();
compilerOracle_init();
compilationPolicy_init();
compileBroker_init();
VMRegImpl::set_regName();
if (!universe_post_init()) {
return JNI_ERR;
}
javaClasses_init(); // must happen after vtable initialization
stubRoutines_init2(); // note: StubRoutines need 2-phase init
// All the flags that get adjusted by VM_Version_init and os::init_2
// have been set so dump the flags now.
if (PrintFlagsFinal) {
CommandLineFlags::printFlags(tty, false);
}
return JNI_OK;
}
- Management初始化
- 字节码初始化
- 类加载器初始化
- 代码缓存初始化
- VM版本初始化
- 操作系统全局初始化
- 堆栈初始化,metaspace初始化,SymbolTable,StringTable初始化
- 解释器初始化
- 标记清除初始化1
- 访问修饰符初始化
- 模板表初始化
- 接口支持初始化
- referenceProcessor初始化
- jni处理初始化 等等。。。。