当我们编写完java源代码程序后,经过javac编译后,执行java命令执行这个程序时,是怎么一步步的调用到我们程序中的main方法的呢?今天通过查看OpenJdk的源码来揭开它的神秘面纱。
java命令是在安装jre/jdk时配置到系统环境路径中去的,执行java命令时会找到bin目录下的java可执行程序,并将我们编译后的java程序类名传递进去就可以执行了。
java可执行程序是由C++编写的,它的内部会启动一个Java虚拟机实例。
虚拟机启动入口函数位于src/java.base/share/native/launcher/main.c。
// src/java.base/share/native/launcher/main.c
// java程序启动入口主函数
JNIEXPORT int main(int argc, char **argv) {
...
return JLI_Laucher(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)
}
// src/java.base/share/native/libjli/java.c
JNIEXPORT int JNICALL JLI_Launch(int argc,
char** argv,
int jargc,
const char** jargv,
int appclassc,
const char** appclassv,
const char* fullversion,
const char* dotversion,
const char* pname,
const char* lname,
jboolean javaargs,
jboolean cpwildcard,
jboolean javaw,
jint ergo) {
...
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
int ContinueInNewThread(InvocationFunction* ifn,
jlong threadStackSize,
int argc,
char **argv,
int mode,
char *what,
int ret) {
int rslt;
...
rslt = CallJavaMainInNewThread(threadStackSize, (void*)&args);
return (ret != 0) ? ret : rslt;
}
//真正调用Java类的main函数入口
int JavaMain(void* _args) {
JNIEnv *env = 0;
jclass mainClass = NULL;
//找到main函数所在的类
mainClass = LoadMainClass(env, mode, what);
//获取main函数的参数
mainArgs = CreateApplicationArgs(env, argv, argc);
//从类中找到main方法标识
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
//调用main方法
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
}
// src/java.base/macosx/native/libjli/java_md_macosx.m
// src/java.base/unix/native/libjli/java_md_solinux.c
int JVMInit(InvocationFunctions* ifn, jlkong threadStackSize, int argc,
char **argv, int mode, char **what, int ret) {
...
return continueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}
CallJavaMainInNewThread(jlong stack_size, void* args) {
int rslt;
...
rslt = JavaMain(args);
return rslt;
}
//hotspot/share/prims/jni.cpp
//调用一个main这个静态方法
static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
JavaCalls::call(result, method, &java_args, CHECK);
}
// hotspot/share/runtime/javaCalls.cpp
void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
os::os_exception_wrapper(call_helper, result, method, args, THREAD);
}
void JavaCalls::call_helper(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
//字节码解释器入口函数地址
address entry_point = method->from_interpreted_entry();
if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
entry_point = method->interpreter_entry();
}
...
////通过call_stub->entry_point->method的调用链,完成Java方法的调用
StubRoutines::call_stub()(
(address)&link,//call_stub调用完后,返回值通过link指针带回来
// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
parameter_address,
args->size_of_parameters(),
CHECK
);
result = link.result();//获取返回值
}
// hotspot/share/runtime/stubRoutines.hpp
// 将_call_stub_entry指针转换为CallStub类型,并执行该指针对应的函数
// 这个_call_stub_entry指针是通过stubGenerator类在初始化生成的,
// 这个stubGernerator负责为将要执行的方法创建栈帧,其实现区分不同CPU平台
static CallStub call_stub() {
return CAST_TO_FN_PTR(CallStub, _call_stub_entry);
}
// Calls to Java
typedef void (*CallStub)(
address link,
intptr_t* result,
BasicType result_type,
Method* method,
address entry_point,
intptr_t* parameters,
int size_of_parameters,
TRAPS
);
这里以x86_32平台为例进行说明_call_stub_entry的创建:
// hotspot/cpu/x86/stubGenerator_x86_32.cpp
class StubGenerator: public StubCodeGenerator {
public:
//构造函数
StubGenerator(CodeBuffer* code, bool all) : StubCodeGenerator(code) {
if (all) {
generate_all();
} else {
generate_initial();
}
}
//初始化
void generate_initial() {
...
//创见CallStub实例,并赋值给StubRoutines::_call_stub_entry
StubRoutines::_call_stub_entry =
generate_call_stub(StubRoutines::_call_stub_return_address);
...
}
}
//创建方法调用的栈帧
address generate_call_stub(address& return_address) {
//创建栈帧、参数入栈等
...
__ movptr(rbx, method); // 保存方法指针到rbx中
__ movptr(rax, entry_point); // get entry_point
__ mov(rsi, rsp); // set sender sp
//调用rax寄存器存储的解释器入口函数,这里解释器入口函数就是entry_point指针指向的函数
//解释器就会开始从method指针指向的位置开始执行字节码
__ call(rax);
...
}
下面看一下解释器的入口函数的实现,从前面可以知道解释器入口函数是从method中获取到的。
// hotspot/share/oops/method.hpp
//该方法被内联了,即获取成员变量_from_interpreted_entry的值。
address from_interpreted_entry() const;
inline address Method::from_interpreted_entry() const {
return Atomic::load_acquire(&_from_interpreted_entry);
}
// _from_interpreted_entry是在link_mehtod函数被赋值的。
void Mehthd
那么_from_interpreted_entry是在什么时候被赋值的呢?在链接方法时。
// hotspot/share/oops/method.cpp
void Method::link_method(const methodHandle& h_method, TRAPS) {
...
if (!is_shared()) {
//终于和字节码解释器勾搭上了
//根据h_method的类型取出对应的解释器入口函数
address entry = Interpreter::entry_for_method(h_method);
set_interpreter_entry(entry);
}
...
}
// hotspot/share/interpreter/abstractInterpreter.hpp
//解释器入口函数数组
static address _entry_table[number_of_method_entries];
static MethodKind method_kind(const methodHandle& m);
static address entry_for_kind(MethodKind k){
return _entry_table[k];
}
//从methodHandle中取出方法类型MethodKind,并根据方法类型从_entry_table取出对应的解释器入口函数地址
static address entry_for_method(const methodHandle& m){
return entry_for_kind(method_kind(m));
}
//虚拟机启动时通过该函数填充_entry_table数组
static void set_entry_for_kind(MethodKind k, address e);
那么到底是什么时候调用的set_entry_for_kind函数来初始化的呢?
在文章开头说过,launcher/main.c中的main函数是java程序的启动函数,在main函数中调用了JLI_Launcher函数,在JLI_Launcher会调用LoadJavaVM函数加载虚拟机的动态链接库,并找到创建虚拟机的入口函数JNI_CreateJavaVM存储到结构体InvocationFunctions中。
这个结构体InvocationFunctions会一直当做参数传递到JavaMain函数中。
之后再JavaMain函数中,会根据JNI_CreateJavaVM虚拟机创建函数来初始化虚拟机,此时已经是在一个新的线程中运行了。
下面看一下具体的调用流程:
// src/java.base/share/native/libjli/java.c
JNIEXPORT int JNICALL JLI_Launch(int argc,
char** argv,
int jargc,
const char** jargv,
int appclassc,
const char** appclassv,
const char* fullversion,
const char* dotversion,
const char* pname,
const char* lname,
jboolean javaargs,
jboolean cpwildcard,
jboolean javaw,
jint ergo) {
...
//Java虚拟机动态链接库的路径
char jvmpath[MAXPATHLEN];
//
InvocationFunctions ifn;
//从参数中读取虚拟机运行环境所需的配置
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
//加载Java虚拟机动态链接库,并找到创建虚拟的函数JNI_CreateJavaVM
//这里会区分不同平台和CPU位数,但大体上就是使用dlopen和dlsym这个两个系统调用来实现
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
...
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
下面以macos平台为例看一下LoadJavaVM的实现:
// src/java/base/macosx/native/libjli/java_md_macosx.m
jboolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
//动态链接库文件句柄
void *libjvm;
//判断是否是静态编译,使用dlopen函数打开动态链接库
#ifndef STATIC_BUILD
libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
#else
libjvm = dlopen(NULL, RTLD_FIRST);
#endif
//使用dlsym函数根据函数符号找到对应函数地址
//这里一共获取了三个函数JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs、JNI_GetCreatedJavaVMs
//如果有任何一下缺失都会返回错误,如果成功则将三个函数存储到InvocationFunctions结构体中。
ifn->CreateJavaVM = (CreateJavaVM_t)
dlsym(libjvm, "JNI_CreateJavaVM");
ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
dlsym(libjvm, "JNI_GetCreatedJavaVMs");
return JNI_TRUE;
}
从动态链接库中找到创建虚拟机的入口函数后,会把InvocationFunctions结构体作为参数一路传递到JavaMain函数中,并在其中发起调用。
// src/java.base/share/native/libjli/java.c
int JavaMain(void* _args) {
//将参数强制转换为JavaMainArgs类型
JavaMainArgs *args = (JavaMainArgs *)_args;
//从参数取出InvocationFunctions结构体
InvocationFunctions ifn = args->ifn;
JavaVM *vm = 0;
JNIEnv *env = 0;
...
//初始化Java虚拟机
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
...
}
static jboolean InitializaJVM(JavaVM **pwm, 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;
//调用JNI_CreateJavaVM函数创建虚拟机,该函数内部会转调JNI_CreateJavaVM_inner函数
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
JLI_MemFree(options);
return r == JNI_OK;
}
// hotspot/share/prims/jni.cpp
static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
jint result = JNI_ERR;
//通过Threads的create_vm函数创建虚拟机
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
if (result == JNI_OK) {
JavaThread *thread = JavaThread::current();
*vm = (JavaVM *)(&main_vm);
*(JNIEnv**)penv = thread->jni_environment();
post_thread_start_event(thread);
ThreadStateTransition::transition(thread, _thread_in_vm, _thread_in_native);
}
...
return result;
}
Threads::create_vm函数非常长,里面执行了很多初始化工作。例如
Threads::create_vm函数中做了上面那么多工作,这里为了简单就不讲所有源码都贴出来了。
因为_entry_table数组的填充是在init_globals()函数中调用的,所以只说明一下init_globals()函数的调用路径。
// hotspot/share/runtime/thread.cpp
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
...
//初始化全局模块
jint status = init_globals();
...
return JNI_OK;
}
// hotspot/share/runtime/init.cpp
jinit init_globals() {
...
//初始化方法处理适配器
MethodHandles::generate_adapters();
...
}
// hotspot/share/prims/methodHandles.cpp
MethodHandlesAdapterBlob* MethodHandles::_adapter_code = NULL;
void MethodHeandles::generate_adapters() {
_adapter_code = MethodHandlesAdapterBlob::create(adapter_code_size);
CodeBuffer code(_adapter_code);
MethodHandlesAdapterGenerator g(&code);
g.generate();
}
void MethodHandlesAdapterGenerator::generate() {
//1.生成通用方法处理适配器
//2.生成解释器入口点
//这里的MethodKinds是一个枚举类型,声明了虚拟机固有的方法类型
for (Interpreter::MethodKind mk = Interpreter::method_handle_invoke_FIRST;
mk <= Interpreter::method_handle_invoke_LAST;
mk = Interpreter::MethodKind(1 + (int)mk)) {
vmIntrinsics::ID iid = Interpreter::method_handle_intrinsic(mk);
StubCodeMark mark(this, "MethodHandle::interpreter_entry", vmIntrinsics::name_at(iid));
//根据方法类型ID生成对应的方法处理解释器入口点,并通过set_entry_for_kind设置到abstractInterpreter.cpp中的_entry_table数组中。
address entry = MethodHandles::generate_method_handle_interpreter_entry(_masm, iid);
if (entry != NULL) {
Interpreter::set_entry_for_kind(mk, entry);
}
}
}
// hotspot/share/interpreter/abstractInterpreter.cpp
void AbstractInterpreter::set_entry_for_kind(AbstractInterpreter::MethodKind kind, address entry) {
//将解释器入口点填充到_entry_table数组中
_entry_table[kind] = entry;
update_cds_entry_table(kind);
}
到此就可以总结一下了,当我们通过java命令执行一个应用程序时,首先会先启动虚拟机实例,启动过程中包含了很多初始化工作,这些工作是为java程序提供运行环境的必要条件。在初始化工作中会根据不同的方法类型构建对应解释器入口点,并存储到一个数组_entry_table中。
当初始化工作完成后,会调用java应用程序的入口方法(static void main(String[] args)),然后根据main方法的类型从_entry_table数组中找出对应的解释器入口点,然后就开始解释执行main方法的字节码了。
最后介绍一下JVM中都预定义了哪些方法类型。
// hotspot/share/interpreter/abstractInterpreter.hpp
enum MethodKind {
//大多数没有声明为native和synchronized方法都属于这种类型
//在执行之前要将局部变量初始化为0
zerolocals, // method needs locals initialization
zerolocals_synchronized, // method needs locals initialization & is synchronized
native, // native method
native_synchronized, // native method & is synchronized
//空方法,也单独由特定解释器处理,避免创建无效的栈帧
empty, // empty method (code: _return)
//成员变量的get方法
accessor, // accessor method (code: _aload_0, _getfield, _(a|i)return)
abstract, // abstract method (throws an AbstractMethodException)
method_handle_invoke_FIRST, // java.lang.invoke.MethodHandles::invokeExact, etc.
method_handle_invoke_LAST = (method_handle_invoke_FIRST
+ (vmIntrinsics::LAST_MH_SIG_POLY
- vmIntrinsics::FIRST_MH_SIG_POLY)),
//一些固定作用的方法,直接指定特定的解释器入口,提高效率
java_lang_math_sin, // implementation of java.lang.Math.sin (x)
java_lang_math_cos, // implementation of java.lang.Math.cos (x)
java_lang_math_tan, // implementation of java.lang.Math.tan (x)
java_lang_math_abs, // implementation of java.lang.Math.abs (x)
java_lang_math_sqrt, // implementation of java.lang.Math.sqrt (x)
java_lang_math_log, // implementation of java.lang.Math.log (x)
java_lang_math_log10, // implementation of java.lang.Math.log10 (x)
java_lang_math_pow, // implementation of java.lang.Math.pow (x,y)
java_lang_math_exp, // implementation of java.lang.Math.exp (x)
java_lang_math_fmaF, // implementation of java.lang.Math.fma (x, y, z)
java_lang_math_fmaD, // implementation of java.lang.Math.fma (x, y, z)
java_lang_ref_reference_get, // implementation of java.lang.ref.Reference.get()
java_util_zip_CRC32_update, // implementation of java.util.zip.CRC32.update()
java_util_zip_CRC32_updateBytes, // implementation of java.util.zip.CRC32.updateBytes()
java_util_zip_CRC32_updateByteBuffer, // implementation of java.util.zip.CRC32.updateByteBuffer()
java_util_zip_CRC32C_updateBytes, // implementation of java.util.zip.CRC32C.updateBytes(crc, b[], off, end)
java_util_zip_CRC32C_updateDirectByteBuffer, // implementation of java.util.zip.CRC32C.updateDirectByteBuffer(crc, address, off, end)
java_lang_Float_intBitsToFloat, // implementation of java.lang.Float.intBitsToFloat()
java_lang_Float_floatToRawIntBits, // implementation of java.lang.Float.floatToRawIntBits()
java_lang_Double_longBitsToDouble, // implementation of java.lang.Double.longBitsToDouble()
java_lang_Double_doubleToRawLongBits, // implementation of java.lang.Double.doubleToRawLongBits()
number_of_method_entries,
invalid = -1
};