[Inside HotSpot] hotspot的启动流程与main方法调用
hotspot的启动流程与main方法调用
虚拟机的使命就是执行public static void main(String[])方法,从虚拟机创建到main方法执行会经过一系列流程。这篇文章详细讨论了执行命令行java.exe HelloWorld调用main函数输出经历了什么。源码使用openjdk12,操作系统为windows 64bits,其它系统和源码版本大同小异。
java.base
首先要明白一个概念,java.exe大体上可以分为启动器部分和hotspot部分。
启动器负责执行一些命令行解析,环境初始化等任务,hotspot部分则是真正的虚拟机干活的地方。
启动器是用C++写的,如果不修改链接器入口点名字,执行java.exe xxx追根溯源必然会跟踪到main函数。这个main位于
openjdk12\src\java.base\share\native\launcher\main.c,这里就是java启动器的最终起源了:
#ifdef JAVAW
char **__initenv;
int WINAPI
WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow)
{
int margc;
char** margv;
int jargc;
char** jargv;
const jboolean const_javaw = JNI_TRUE;
__initenv = _environ;
#else /* JAVAW */
JNIEXPORT int
main(int argc, char **argv)
{
int margc;
char** margv;
int jargc;
char** jargv;
const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
// 处理传递给启动器的参数
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);
}
如果用户执行的是javaw.exe就进入WinMain入口,否则java.exe进入main入口。
在main中会处理启动器的参数比如这种-XX:+UnlockDiagnosticVMOptions -XX:+PauseAtExit ,处理完之后调用JLI_Launcher。多说一点,启动器代码也分为系统相关和系统无关,像java.base/linux,java.base/windows这种就是平台相关,java.base/share就是平台无关代码。
java.base -> JLI_Launcher
JLI_Launcher位于openjdk12\src\java.base\share\native\libjli\java.c,
JNIEXPORT int JNICALL
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_class /* ergnomics policy */
);
JLI_Launcher做了很多重要的事情
- 定位jre
- 解析命令行,比如传入
-XX:SDKGJ导致虚拟机退出就是在这里发生的。 - 加载jvm.dll,获取JNI_CreateJavaVM等函数地址
第三点非常重要,它是启动器调用hotspot JNI的桥梁。说着这么夸张,其实做起来是非常简单的,就是LoadLibrary()加载jvm.dll然后GetProcAddress()运行时获取JNI_CreateJavaVM地址转化为函数指针,对应linux的dlopen,dlsym。然后经过一些中转,启动器会走到JavaMain。
jaba.base -> JLI_Launcher -> JavaMain
JavaMain维护hotspot的一个生命周期,它沟通java启动器与hotspot世界,完成java.exe的功能:
int JNICALL
JavaMain(void * _args)
{
...
/* 初始化虚拟机 */
start = CounterGet();
/* 这里初始化虚拟机调用的就是之前提到的在jvm.dll里面获取到的JNI_CreateJavaVM函数指针
* 可以说,这里的JNI_CreateJavaVM是hotspot世界最先出现的地方
*/
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
/* 对虚拟机启动进行性能profiling */
if (JLI_IsTraceLauncher()) {
end = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
(long)(jint)Counter2Micros(end-start));
}
ret = 1;
/* 加载main函数所在的类 */
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/* 对GUI程序的支持 */
appClass = GetApplicationClass(env);
mainArgs = CreateApplicationArgs(env, argv, argc);
if (dryRun) {
ret = 0;
LEAVE();
}
PostJVMInit(env, appClass, vm);
/* 获取main方法id */
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
/* main方法调用 */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
/* 启动器的返回值(非System.exit退出) */
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();
}
JavaMain这个函数做了我们通常意义上所认为启动器应该做的事情,它:
- 初始化虚拟机,
- 获取main所在的类
- 调用main方法
- 处理返回值
到这里java启动器流程基本上已经清晰了,但是旅程并未结束。除了java启动器外,本文还想探究一下main方法的调用。
jaba.base -> JLI_Launcher -> JavaMain -> CallStaticVoidMethod
首先,欢迎来到hotspot的世界。前面说到main方法的调用是这么一行代码:
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
那么它是怎么进入hotspot的世界的呢,要回答这个问题得看看env这是个什么东西。
env类似于这样一个结构:
struct P{
void (*jni_f1)(int,int);
void (*jni_f2)();
void (*jni_f3)(double);
};
P* env;
然后(*env)->jni_f1(3,4)调用的就是这个jni_f1函数指针,这些指针指向的是hotspot/share/primes/jni.cpp里面的入口点。
jaba.base -> JLI_Launcher -> JavaMain -> CallStaticVoidMethod -> JavaCalls::call
回到上面的代码,CallStaticVoidMethod函数指针指向的就是JNI里面的函数:
JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...))
JNIWrapper("CallStaticVoidMethod");
HOTSPOT_JNI_CALLSTATICVOIDMETHOD_ENTRY(env, cls, (uintptr_t) methodID);
DT_VOID_RETURN_MARK(CallStaticVoidMethod);
va_list args;
va_start(args, methodID);
JavaValue jvalue(T_VOID);
JNI_ArgumentPusherVaArg ap(methodID, args);
jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);
va_end(args);
JNI_END
static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));
// 创建java调用的参数
ResourceMark rm(THREAD);
int number_of_parameters = method->size_of_parameters();
JavaCallArguments java_args(number_of_parameters);
args->set_java_argument_object(&java_args);
assert(method->is_static(), "method should be static");
args->iterate( Fingerprinter(method).fingerprint() );
// 初始化返回值类型
result->set_type(args->get_ret_type());
// main方法调用
JavaCalls::call(result, method, &java_args, CHECK);
// 返回值转换
if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
}
}
jni_CallStaticVoidMethod只是处理了一下可变参数,其他工作交给jni_invoke_static。这个函数会把之前传入的命令行参数转换为虚拟机里面的oop对象,然后最终通过JavaCalls::call调用了main函数。这不是个特例,java的所有方法调用都是通过JavaCalls::call调用的,它会创建解释执行所需的栈帧,然后识别hotspot的模板解释器入口点,进入这个入口点执行字节码,当然这都是后话,如果有的话...
[Inside HotSpot] hotspot的启动流程与main方法调用的更多相关文章
- 使用main方法调用http请求本地服务器的某个servlet报错问题
java.io.IOException: Server returned HTTP response code: 500 for URL: http://localhost:8081/test/myS ...
- 用Main方法调用freemarker生成文件
MyGenerator.java package com.comp.common; import java.io.BufferedWriter; import java.io.File; import ...
- java 主类的main方法调用其他方法
方法1:A a=new test().new A(); 内部类对象通过外部类的实例对象调用其内部类构造方法产生,如下: public class test{ class A{ void fA(){ S ...
- 简单的main方法调用一个加减法函数背后的细节
测试程序 /* * AddTest.c * * Created on: 2019年10月13日 * Author: appweb */ #include <stdio.h> int add ...
- 老李推荐:第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览
老李推荐:第5章2节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动流程概览 每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就 ...
- 女儿拿着小天才电话手表问我App启动流程
前言 首先,new一个女儿, var mDdaughter = new 女儿("6岁","漂亮可爱","健康乖巧","最喜欢玩小天 ...
- dotnet 为大型应用接入 ApplicationStartupManager 启动流程框架
对于大型的应用软件,特别是客户端应用软件,应用启动过程中,需要执行大量的逻辑,包括各个模块的初始化和注册等等逻辑.大型应用软件的启动过程都是非常复杂的,而客户端应用软件是对应用的启动性能有所要求的,不 ...
- [译]C# 7系列,Part 2: Async Main 异步Main方法
原文:https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main/ 你大概知道,C#语言可以构建两种 ...
- 【细说Java】揭开Java的main方法神秘的面纱
大家都知道,main方法是Java应用程序的入口,其定义格式为: public static void main(String[] args) 可是为什么要这么定义呢?不这样定义可以么?main方法可 ...
随机推荐
- 解决持久化数据太大,单个节点的硬盘无法存储的问题;解决运算量太大,单个节点的内存、CPU无法处理的问题
需要学习的技术很多,要自学新知识也不是一件容易的事,选择一个自己比较感兴趣的会是一个比较好的开端,于是,打算学一学分布式系统. 带着问题,有目的的学习,先了解整体架构,在深入感兴趣的细节,这是我的计划 ...
- jmeter添加断言
先创建一个线程组,再创建一个http请求. 为了方便观察,我们添加两个监听器,察看结果树和断言结果. 添加断言:响应断言,响应断言也是比较常用的一个断言 设置响应断言:正常情况下响应代码是200.选择 ...
- EF生成模型出现异常:表“TableDetails“中列“IsPrimaryKey”的值为DBNull解决方法
Entity Framework连接MySQL时:由于出现以下异常,无法生成模型:"表"TableDetails"中列"IsPrimaryKey"的值 ...
- infolite(中文检索系统)~爬虫利器
infolite 今天为大家分享一个爬虫利器-infolite.这是一个chrome浏览器的插件,如果你在写爬虫的时候对复杂繁琐的控件路径分析是深恶痛绝.那么infolite绝对是你最好的选择. 安装 ...
- Java 字符流文件读写
上篇文章,我们介绍了 Java 的文件字节流框架中的相关内容,而我们本篇文章将着重于文件字符流的相关内容. 首先需要明确一点的是,字节流处理文件的时候是基于字节的,而字符流处理文件则是基于一个个字符为 ...
- 一支烟的时间导致他错失女神,Python查看撤回消息,力挽狂澜!
2011年1月21日 微信(WeChat) 是腾讯公司于2011年1月21日推出的一个为智能终端提供即时通讯服务的免费应用程序,由张小龙所带领的腾讯广州研发中心产品团队打造 .在互联网飞速发展的下.民 ...
- 【bzoj 2916】[Poi1997]Monochromatic Triangles
题目描述 空间中有n个点,任意3个点不共线.每两个点用红线或者蓝线连接,如果一个三角形的三边颜色相同,那么称为同色三角形.给你一组数据,计算同色三角形的总数. 输入 第 ...
- mysql输入中文出现ERROR 1366
MySQL输入中文出现如下错误: ERROR 1366: 1366: Incorrect string value: '\xE6\xB0\xB4\xE7\x94\xB5...' for column ...
- XSS过滤JAVA过滤器filter 防止常见SQL注入
Java项目中XSS过滤器的使用方法. 简单介绍: XSS : 跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩 ...
- MYSQL——数据库存储引擎!
本人安装mysql版本为:mysql Ver 14.14 Distrib 5.7.18, for Win64 (x86_64),查看mysql的版本号方式:cmd-->mysql --vers ...