这篇文章是从 OpenJDK 源码的角度讲当我们执行了

java -classpath . hello

之后,java.exe 怎样从 main 函数開始运行,启动虚拟机,并运行字节码中的代码。

实验环境

要了解一个系统是怎样执行的,光看是不行的,要实际地执行,调试,改动才干对系统的动作方式有所了解。

起初我是依照 GitHub 上的一个项目 OpenJDK-Research 在 windows 7 64位平台上,使用 Visual Studio 2010 来调试,执行的。可是后来发现,这个项目只编译了HotSpot虚拟机, java.exe 并没有编译。

这里我们首先弄明确 java.exe 和虚拟机之间的关系。我们使用 Visual Studio 编译出的 HotSpot 是虚拟机,是作为动态链接库的形式被 java.exe 载入的。java.exe 负责解析參数,载入虚拟机链接库,它须要调用虚拟机中的函数来完毕运行
Java 程序的功能。所以,你在HotSpot的源码中找不到启动的程序的 main 函数,本来在 openjdk7 中,虚拟机是带有一个启动器的,在文件夹 openjdk/hotspot/src/share/tools/launcher/java.c 中能够找到
main 函数,可是在 openjdk8 中,这个启动器不见了,被放在 openjdk/jdk 文件夹下,而不是 openjdk/hotspot 文件夹下了,给我们的学习过程造成了伤害。

所以我后来就在 linux 平台上调试了,由于在 windows 平台上,我始终没有把整个 openjdk8 编译成功,编译不出java.exe, 只编译了 hotspot,是看不到从
main 函数開始的运行的。关于怎样在 linux 平台下编译调试 openjdk8,能够參考我的还有一篇文章 在Ubuntu 12.04 上编译 openjdk8.

调用栈

jdk8u/jdk/src/share/bin/main.c::WinMain/main
jdk8u/jdk/src/share/bin/java.c::JLI_Launch
jdk8u/jdk/src/solaris/bin/java_md_solinux.c::LoadJavaVM # Load JVM Library: libjvm.so
jdk8u/jdk/src/solaris/bin/java_md_solinux.c::JVMInit # Create JVM
jdk8u/jdk/src/share/bin/java.c::ContinueInNewThread
jdk8u/jdk/src/solaris/bin/java_md_solinux.c::ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args)
jdk8u/jdk/src/share/bin/java.c::JavaMain
jdk8u/jdk/src/share/bin/java.c::InitializeJVM
jdk8u\hotspot\src\share\vm\prims\jni.cpp::JNI_CreateJavaVM

运行过程

  • main.c (jdk8u/jdk/src/share/bin/main.c)
#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);
}

这就是传说中的 main 函数的真身,能够看出,它针对操作系统是否使用 Windows ,运行了不同的代码段,终于调用JLI_Launch 函数。

  • JLI_Lanuch(jdk8u/jdk/src/share/bin/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 */
)
{ ... if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
} ... return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret); }

从这里能够看出 JLI_Lanuch 的各个參数的含义, 我列出了关键代码, 当中 LoadJavaVM 完毕加载虚拟机动态链接库,并初始化 ifn 中的函数指针,HotSpot虚拟机就是这样向启动器 java 提供功能。

  • LoadJavaVM (jdk8u/jdk/src/solaris/bin/java_md_solinux.c)

这个函数涉及动态链接库,不同操作系统有不同接口,这里是针对 linux 的。

jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
... libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL); ... ifn->CreateJavaVM = (CreateJavaVM_t)
dlsym(libjvm, "JNI_CreateJavaVM"); ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs"); ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
dlsym(libjvm, "JNI_GetCreatedJavaVMs"); ...

从这里能够看出加载动态链接库以及初始化 ifn 数据结构的代码。在我的调试版本号中,javapath 指向之前编译出的动态链接库 jdk8u/build/fastdebug/jdk/lib/i386/server/libjvm.so.

  • JVM_Init(jdk8u/jdk/src/solaris/bin/java_md_solinux.c)

回到 JLI_Lanuch 函数,我们终于进入 JVM_Init,
这个函数会启动一个新线程。

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);
}

ContinueInNewThread 会调用还有一个函数 ContinueInNewThread0 启动线程,运行 JavaMain 函数:

int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) { ... if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp);
rslt = (int)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 continuation as JNI_CreateJavaVM needs to create quite a
* few new threads, anyway, just give it a try..
*/
rslt = continuation(args);
} ...
  • JavaMain(jdk8u/jdk/src/share/bin/java.c)

这个函数会初始化虚拟机,载入各种类,并运行应用程序中的 main 函数。凝视非常具体。

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, end; RegisterThread(); /* Initialize the virtual machine */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
} ... ret = 1; /*
* Get the application's main class.
*
* See bugid 5030265. The Main-Class name has already been parsed
* from the manifest, but not parsed properly for UTF-8 support.
* Hence the code here ignores the value previously extracted and
* uses the pre-existing code to reextract the value. This is
* possibly an end of release cycle expedient. However, it has
* also been discovered that passing some character sets through
* the environment has "strange" behavior on some variants of
* Windows. Hence, maybe the manifest parsing code local to the
* launcher should never be enhanced.
*
* Hence, future work should either:
* 1) Correct the local parsing code and verify that the
* Main-Class attribute gets properly passed through
* all environments,
* 2) Remove the vestages of maintaining main_class through
* the environment (and remove these comments).
*
* This method also correctly handles launching existing JavaFX
* applications that may or may not have a Main-Class manifest entry.
*/
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/*
* In some cases when launching an application that needs a helper, e.g., a
* JavaFX application with no main method, the mainClass will not be the
* applications own main class but rather a helper class. To keep things
* consistent in the UI we need to track and report the application main class.
*/
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1);
/*
* PostJVMInit uses the class name as the application name for GUI purposes,
* for example, on OSX this sets the application name in the menu bar for
* both SWT and JavaFX. So we'll pass the actual application class here
* instead of mainClass as that may be a launcher or helper class instead
* of the application class.
*/
PostJVMInit(env, appClass, vm);
/*
* The LoadMainClass not only loads the main class, it will also ensure
* that the main method's signature is correct, therefore further checking
* is not required. The main method is invoked here so that extraneous java
* stacks are not in the application stack trace.
*/
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID); /* Build platform specific argument array */
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs); /* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); /*
* The launcher's exit code (in the absence of calls to
* System.exit) will be non-zero if main threw an exception.
*/
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();
}

注意 InitializeJVM 函数,它会调用之前初始化的 ifn 数据结构中的 CreateJavaVM 函数.

  • InitializeJVM(jdk8u/jdk/src/share/bin/java.c::InitializeJVM)
static jboolean
InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
JavaVMInitArgs args;
jint r; memset(&args, 0, sizeof(args));
args.version = JNI_VERSION_1_2;
args.nOptions = numOptions;
args.options = options;
args.ignoreUnrecognized = JNI_FALSE; if (JLI_IsTraceLauncher()) {
int i = 0;
printf("JavaVM args:\n ");
printf("version 0x%08lx, ", (long)args.version);
printf("ignoreUnrecognized is %s, ",
args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
printf("nOptions is %ld\n", (long)args.nOptions);
for (i = 0; i < numOptions; i++)
printf(" option[%2d] = '%s'\n",
i, args.options[i].optionString);
} r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
JLI_MemFree(options);
return r == JNI_OK;
}

ifn->CreateJavaVM指向虚拟机动态链接库中的 JNI_CreateJavaVM 函数,这个函数会真正创建虚拟机。
这个函数运行后,pvm, penv 的值就会被设定,我们能够比較下运行前后它们的值,来看看它们的作用。

// before r = ifn->CreateJavaVM(pvm, (void **)penv, &args);

(gdb) p *pvm
$8 = (JavaVM *) 0x0
(gdb) p *penv
$9 = (JNIEnv *) 0x0
// after r = ifn->CreateJavaVM(pvm, (void **)penv, &args);

(gdb) p ***penv
$14 = {reserved0 = 0x0, reserved1 = 0x0, reserved2 = 0x0, reserved3 = 0x0,
GetVersion = 0xb6ede599 <jni_GetVersion>,
DefineClass = 0xb6eb20a0 <jni_DefineClass>,
FindClass = 0xb6eb253c <jni_FindClass>,
FromReflectedMethod = 0xb6eb2b17 <jni_FromReflectedMethod>,
FromReflectedField = 0xb6eb2edb <jni_FromReflectedField>,
...
...
} (gdb) p ***pvm
$15 = {reserved0 = 0x0, reserved1 = 0x0, reserved2 = 0x0,
DestroyJavaVM = 0xb6edf1e8 <jni_DestroyJavaVM>,
AttachCurrentThread = 0xb6edf69a <jni_AttachCurrentThread>,
DetachCurrentThread = 0xb6edf795 <jni_DetachCurrentThread>,
GetEnv = 0xb6edf8d3 <jni_GetEnv>,
AttachCurrentThreadAsDaemon = 0xb6edfa7d <jni_AttachCurrentThreadAsDaemon>}

能够看出它们得到了hotspot 中以 jni_ 开头的一些函数,虚拟机正是以这种方式向外提供功能。我们大概看一下JNI_CreateJavaVM 的功能。


  • JNI_CreateJavaVM(jdk8u\hotspot\src\share\vm\prims\jni.cpp)
_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
... 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);
} ... } ... }

当中的 create_vm 函数是虚拟机初始化的关键,它初始化了虚拟机的大部分组件。另外能够看到
vm, penv 的值被设定。

这个函数位于 jdk8u\hotspot\src\share\vm\prims\jni.cpp

我之前在 Windows 下调试,直接调试的 HotSpot 动态链接库,能够看到的第一个函数就是 JNI_CreateJavaVM, 之前的调用都位于 java.exe 代码中。由于
Windows 中 java.exe 不是我们自己编译的,看不到当中调用关系。例如以下图所看到的:

同一时候能够看到两个线程

Java虚拟机的启动与程序的执行的更多相关文章

  1. java虚拟机jvm启动后java代码层面发生了什么?

    java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, ...

  2. 如何设置Java虚拟机JVM启动内存参数

    Tomcat默认的Java虚拟机JVM启动内存参数大约只有64MB或者128MB,非常小,远远没有利用现在服务器的强大内存,所以要设置Java虚拟机JVM启动内存参数.具体设置方法为: Tomcat修 ...

  3. 深入理解Java虚拟机06--虚拟机字节码执行引擎

    一.前言 物理机的执行引擎是直接在物理硬件如CPU.操作系统.指令集上运行的,但是对于虚拟机来讲,他的执行引擎由自己实现. 执行引擎有统一的外观(Java虚拟机规范),不同类型的虚拟机都遵循了这一规范 ...

  4. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  5. Java虚拟机JVM内存分区及代码执行机制

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt230 1.  JVM体系结构 图1 JVM体系结构    方法区:存放JVM ...

  6. 重读《深入理解Java虚拟机》五、虚拟机如何执行字节码?程序方法如何被执行?虚拟机执行引擎的工作机制

    Class文件二进制字符流通过类加载器和虚拟机加载到内存(方法区)完成在内存上的布局和初始化后,虚拟机字节码执行引擎就可以执行相关代码实现程序所定义的功能.虚拟机执行引擎执行的对象是方法(均特指非本地 ...

  7. Java程序猿从笨鸟到菜鸟之(九十二)深入java虚拟机(一)——java虚拟机底层结构具体解释

    本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 在曾经的博客里面,我们介绍了在java领域中大部分的知识点,从最基础的java最基本的语法 ...

  8. Java虚拟机启动过程解析

    一.序言 当我们在编写Java应用的时候,很少会注意Java程序是如何被运行的,如何被操作系统管理和调度的.带着好奇心,探索一下Java虚拟机启动过程. 1.素材准备 从Java源代码.Java字节码 ...

  9. 【深入理解JAVA虚拟机】第三部分.虚拟机执行子系统.1.类文件结构

    无关性 无关性的体现有两个方面: 1.平台无关性:可在不同的操作系统和机器指令集上执行,可在不同厂商的虚拟机平台上执行. 2.语言无关性:用不同编程语言写出的代码编译生成的文件都可以运行. 实现思想: ...

随机推荐

  1. [转]Java Web乱码过滤器

    本文转自http://blog.csdn.net/l271640625/article/details/6388690 大家都知道,在jsp里乱码是最让人讨厌的东西,有些乱码出来的莫名其妙,给开发带来 ...

  2. 帮哥们做的一个整理文档的小工具(C++ string的标准函数还是很给力的,代码在最下)

    其实把程序用到生活中,真的能节约不少时间!程序的力量是无穷滴! 哥们的毕业设计是要做法律文书匹配之类的东东,有一步是要抽取所有的法律法规名称,而刚好我们要处理的文件中,法规的名称之前都有个‘.‘,所以 ...

  3. 【C#】如何创建xml文件以及xml文件的增、改

    增: using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpre ...

  4. 机器学习中的算法-决策树模型组合之随机森林与GBDT

    机器学习中的算法(1)-决策树模型组合之随机森林与GBDT 版权声明: 本文由LeftNotEasy发布于http://leftnoteasy.cnblogs.com, 本文可以被全部的转载或者部分使 ...

  5. 一款APP从设计稿到切图过程全方位揭秘 Mark

    纯干货!一款APP从设计稿到切图过程全方位揭秘   @BAT_LCK:我本身是一名GUI设计师,所以我只站在GUI设计师的角度去把APP从项目启动到切片输出的过程写一写,相当于工作流程的介绍吧.公司不 ...

  6. leetcode–jump game II

    1.题目描述 Given an array of non-negative integers, you are initially positioned at the first index of t ...

  7. Linux文件系统 (Ubunt)

    Linux 文件系统是linux的一个十分基础的知识,同时也是学习linux的必备知识. 本文将站在一个较高的视图来了解linux的文件系统,主要包括了linux磁盘分区和目录.挂载基本原理.文件存储 ...

  8. 第三百四十五天 how can I 坚持

    最烦这个阶段了,飘忽不定,或许这种感觉未来会很值得回味. 我为什么会那么烦,是因为错过而悔恨,还是..其实我还是很在乎的,好想一切都随缘. 让我讲struts.springmvc,可是什么都不会. 我 ...

  9. Determining Equality of Objects

    [Determining Equality of Objects] If you need to determine whether one object is the same as another ...

  10. webconfig文件serviceHostingEnvironment节点出错的解决方法

    在三点五和二版本的配置中可以出现这个节点,但是在4.0是没有的,所以如果框架是4.0的时候要除去这个节点,不然就会报以下错误: Configuration Error Description: An ...