HTML5技术

Java Main如何被执行? - iceAeterna

字号+ 作者:H5之家 来源:博客园 2015-11-05 10:22 我要评论( )

java应用程序的启动在在/hotspot/src/share/tools/launcher/java.c的main()函数中,而在虚拟机初始化过程中,将创建并启动Java的Main线程。最后将调用JNIEnv的CallStaticVoidMethod()来执行main方法。 CallStaticVoidMethod()对应的jni函数为jni_CallStaticV

  java应用程序的启动在在/hotspot/src/share/tools/launcher/java.c的main()函数中,而在虚拟机初始化过程中,将创建并启动Java的Main线程。最后将调用JNIEnv的CallStaticVoidMethod()来执行main方法。

CallStaticVoidMethod()对应的jni函数为jni_CallStaticVoidMethod,定义在/hotspot/src/share/vm/prims/jni.cpp中,而jni_CallStaticVoidMethod()又调用了jni_invoke_static(),jni_invoke_static()通过JavaCalls的call()发起对Java方法的调用

  所有来自虚拟机对Java函数的调用最终都将由JavaCalls模块来完成,JavaCalls将通过call_helper()来执行Java方法并返回调用结果,并最终调用StubRoutines::call_stub()来执行Java方法:

{ JavaCallWrapper link(method, receiver, result, CHECK); StubRoutines::call_stub()( 6 (address)&link, result_val_address, result_type, 10 method(), 11 entry_point, 12 args->parameters(), 13 args->size_of_parameters(), 14 CHECK 15 ); 16 17 result = link.result(); // circumvent MS C++ 5.0 compiler bug (result is clobbered across call) (oop_result_flag) { 20 thread->set_vm_result((oop) result->get_jobject()); 21 } 22 } 23 }

  call_stub()定义在/hotspot/src/share/vm/runtime/stubRoutines.h中,实际上返回的就是CallStub函数指针_call_stub_entry,该指针指向call_stub的汇编实现的目标代码指令地址,即call_stub的例程入口。

// Calls to Java typedef void (*CallStub)( address link, intptr_t* result, BasicType result_type, methodOopDesc* method, address entry_point, intptr_t* parameters, int size_of_parameters, TRAPS ); static CallStub call_stub() { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }

  在分析call_stub的汇编代码之前,先了解下x86寄存器和栈帧以及函数调用的相关知识。 
  x86-64的所有寄存器都是与机器字长(数据总线位宽)相同,即64位的,x86-64将x86的8个32位通用寄存器扩展为64位(eax、ebx、ecx、edx、eci、edi、ebp、esp),并且增加了8个新的64位寄存器(r8-r15),在命名方式上,也从”exx”变为”rxx”,但仍保留”exx”进行32位操作,下表描述了各寄存器的命名和作用

 

 此外,还有16个128位的XMM寄存器,分别为xmm0-15,x84-64的寄存器遵循调用约定(Calling Conventions): 

https://msdn.microsoft.com/en-US/library/zthk2dkh(v=vs.80).aspx 
1.参数传递: 
  (1).前4个参数的int类型分别通过rcx、rdx、r8、r9传递,多余的在栈空间上传递(从右向左依次入栈),寄存器所有的参数都是向右对齐的(低位对齐) 
  (2).浮点数类型的参数通过xmm0-xmm3传递,注意不同类型的参数占用的寄存器序号是根据参数的序号来决定的,比如add(int,double,float,int)就分别保存在rcx、xmm1、xmm2、r9寄存器中 
  (3).8/16/32/64类型的结构体或共用体和_m64类型将使用rcx、rdx、r8、r9直接传递,而其他类型将会通过指针引用的方式在这4个寄存器中传递 
  (4).被调用函数当需要时要把寄存器中的参数移动到栈空间中(shadow space) 
2.返回值传递 
  (1).对于可以填充为64位的返回值(包括_m64)将使用rax进行传递 
  (2).对于_m128(i/d)以及浮点数类型将使用xmm0传递 
  (3).对于64位以上的返回值,将由调用函数在栈上为其分配空间,并将其指针保存在rcx中作为”第一个参数”,而传入参数将依次右移,最后函数调用完后,由rax返回该空间的指针 
  (4).用户定义的返回值类型长度必须是1、2、4、8、16、32、64 
3.调用者/被调用者保存寄存器 
  调用者保存寄存器:rax、rcx、rdx、r8-r11都认为是易失型寄存器(volatile),这些寄存器随时可能被用到,这些寄存器将由调用者自行维护,当调用其他函数时,被调用函数对这些寄存器的操作并不会影响调用函数(即这些寄存器的作用范围仅限于当前函数)。 
  被调用者保存寄存器:rbx、rbp、rdi、rsi、r12-r15、xmm6-xmm15都是非易失型寄存器(non-volatile),调用其他函数时,这些寄存器的值可能在调用返回时还需要用,那么被调用函数就必须将这些寄存器的值保存起来,当要返回时,恢复这些寄存器的值(即这些寄存器的作用范围是跨函数调用的)。

  以如下程序为例,分析函数调用的栈帧布局:

1 double func(int param_i1, float param_f1, double param_d1, int param_i2, double param_d2) 2 3 { 4 int local_i1, local_i2; 5 float local_f1; 6 double local_d1; 7 double local_d2 = 3.0; 8 local_i1 = param_i1; 9 local_i2 = param_i2; 10 local_f1 = param_f1; 11 local_d1 = param_d1; 12 return local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2; 13 } main() 16 17 { 18 double res; 19 res = func(1, 1.0, 2.0, 3, 3.0); ; 21 }

main函数调用func之前的汇编代码如下:

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 如何快速处理线上故障 - 倒骑的驴

    如何快速处理线上故障 - 倒骑的驴

    2017-05-02 12:01

  • 如何在 ASP.NET Core 中发送邮件 - Savorboard

    如何在 ASP.NET Core 中发送邮件 - Savorboard

    2017-05-02 08:02

  • 对于Bootstrap的介绍以及如何使用 - novai-L

    对于Bootstrap的介绍以及如何使用 - novai-L

    2017-04-29 09:00

  • 谈一下我们是如何开展code review的 - HarlanC

    谈一下我们是如何开展code review的 - HarlanC

    2017-04-27 15:03

网友点评
a