Java虚拟机的启动与程序的执行
这篇文章是从 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虚拟机的启动与程序的执行的更多相关文章
- java虚拟机jvm启动后java代码层面发生了什么?
java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, ...
- 如何设置Java虚拟机JVM启动内存参数
Tomcat默认的Java虚拟机JVM启动内存参数大约只有64MB或者128MB,非常小,远远没有利用现在服务器的强大内存,所以要设置Java虚拟机JVM启动内存参数.具体设置方法为: Tomcat修 ...
- 深入理解Java虚拟机06--虚拟机字节码执行引擎
一.前言 物理机的执行引擎是直接在物理硬件如CPU.操作系统.指令集上运行的,但是对于虚拟机来讲,他的执行引擎由自己实现. 执行引擎有统一的外观(Java虚拟机规范),不同类型的虚拟机都遵循了这一规范 ...
- 深入理解java虚拟机(5)---字节码执行引擎
字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...
- Java虚拟机JVM内存分区及代码执行机制
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt230 1. JVM体系结构 图1 JVM体系结构 方法区:存放JVM ...
- 重读《深入理解Java虚拟机》五、虚拟机如何执行字节码?程序方法如何被执行?虚拟机执行引擎的工作机制
Class文件二进制字符流通过类加载器和虚拟机加载到内存(方法区)完成在内存上的布局和初始化后,虚拟机字节码执行引擎就可以执行相关代码实现程序所定义的功能.虚拟机执行引擎执行的对象是方法(均特指非本地 ...
- Java程序猿从笨鸟到菜鸟之(九十二)深入java虚拟机(一)——java虚拟机底层结构具体解释
本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 在曾经的博客里面,我们介绍了在java领域中大部分的知识点,从最基础的java最基本的语法 ...
- Java虚拟机启动过程解析
一.序言 当我们在编写Java应用的时候,很少会注意Java程序是如何被运行的,如何被操作系统管理和调度的.带着好奇心,探索一下Java虚拟机启动过程. 1.素材准备 从Java源代码.Java字节码 ...
- 【深入理解JAVA虚拟机】第三部分.虚拟机执行子系统.1.类文件结构
无关性 无关性的体现有两个方面: 1.平台无关性:可在不同的操作系统和机器指令集上执行,可在不同厂商的虚拟机平台上执行. 2.语言无关性:用不同编程语言写出的代码编译生成的文件都可以运行. 实现思想: ...
随机推荐
- nmon基础
nmon是分析 AIX 和 Linux 性能的免费工具 最简单的安装方式(Ubuntu apt源) sudo apt-get install nmon 在terminal下打开nmon 敲CMD,出现 ...
- linux 常用端口列表
常见端口表汇总 1 tcpmux TCP Port Service Multiplexer 传输控制协议端口服务多路开关选择器 2 compressnet Management Utility com ...
- bzoj1036 树的统计Count
第一次写链剖,于是挑了个简单的裸题写. 以下几点要注意: 1.链剖中的height是从根到该店经过的轻边个数 2.分清num与sum..... #include<cstdio> #incl ...
- git初步使用
git初步使用 主要目的:使用代码控制工具,练习使用git 1.创建新项目 网址如下: https://github.com/kellyseeme?tab=repositories 注意每个人使用的名 ...
- excel导入数据到sqlserver
1.读取excel数据到dataset public static System.Data.DataSet ExcelSqlConnection(string filepath, string tab ...
- Linux系统下查看已经登录用户并踢出的方法
LINUX是个多用户系统,一旦连接到网络中,它可以同时为多个登录用户提供服务. 查看用户的操作 查看当前用户: [ROOT@LOCALHOST ROOT] # W ...
- js 剪切板应用clipboardData
http://blog.csdn.net/fox123871/article/details/6454634 <a href="http://blog.csdn.net/fox1238 ...
- 关于dom节点绑定滑动事件导致浏览器上下滑动失效解决方案--黄丕巧
1.移动端开发往往需要添加一下自定义的左右滑动事件,但是添加了左右滑动事件之后就要阻止浏览器大默认事件,否则dom节点的滑动事件和浏览器本身的滑动会出现冲突,导致滑动的时候会出现消失瞬间再出现的效果 ...
- 关于ES6扩展属性
ES6 let和const let命令 只在代码块作用域内有效 不存在变量提升(不能在申明之前赋值) 暂时性死区(在区域内不受外部变量影响) 不允许重复申明(在相同代码块区域内) 块级作用域 let为 ...
- Xcode集成Google Test
Xcode集成Google Test 1.下载源代码https://github.com/google/googletest 2.进入下载文件目录下googletest/make中运行make gte ...