第4篇-JVM终于开始调用Java主类的main()方法啦


在前一篇 第3篇-CallStub新栈帧的创建 中我们介绍了generate_call_stub()函数的部分实现,完成了向CallStub栈帧中压入参数的操作,此时的状态如下图所示。

继续看generate_call_stub()函数的实现,接来下会加载线程寄存器,代码如下:

__ movptr(r15_thread, thread);
__ reinit_heapbase();

生成的汇编代码如下:

mov    0x18(%rbp),%r15  
mov    0x1764212b(%rip),%r12   # 0x00007fdf5c6428a8

对照着上面的栈帧可看一下0x18(%rbp)这个位置存储的是thread,将这个参数存储到%r15寄存器中。

如果在调用函数时有参数的话需要传递参数,代码如下:

Label parameters_done;
// parameter_size拷贝到c_rarg3即rcx寄存器中
__ movl(c_rarg3, parameter_size);
// 校验c_rarg3的数值是否合法。两操作数作与运算,仅修改标志位,不回送结果
__ testl(c_rarg3, c_rarg3);
// 如果不合法则跳转到parameters_done分支上
__ jcc(Assembler::zero, parameters_done);

// 如果执行下面的逻辑,那么就表示parameter_size的值不为0,也就是需要为
// 调用的java方法提供参数
Label loop;
// 将地址parameters包含的数据即参数对象的指针拷贝到c_rarg2寄存器中
__ movptr(c_rarg2, parameters);       
// 将c_rarg3中值拷贝到c_rarg1中,即将参数个数复制到c_rarg1中
__ movl(c_rarg1, c_rarg3);            
__ BIND(loop);
// 将c_rarg2指向的内存中包含的地址复制到rax中
__ movptr(rax, Address(c_rarg2, 0));
// c_rarg2中的参数对象的指针加上指针宽度8字节,即指向下一个参数
__ addptr(c_rarg2, wordSize);       
// 将c_rarg1中的值减一
__ decrementl(c_rarg1);            
// 传递方法调用参数
__ push(rax);                       
// 如果参数个数大于0则跳转到loop继续
__ jcc(Assembler::notZero, loop);

这里是个循环,用于传递参数,相当于如下代码:

while(%esi){
   rax = *arg
   push_arg(rax)
   arg++;   // ptr++
   %esi--;  // counter--
}

生成的汇编代码如下:

// 将栈中parameter size送到%ecx中
mov    0x10(%rbp),%ecx   
// 做与运算,只有当%ecx中的值为0时才等于0 
test   %ecx,%ecx          
// 没有参数需要传递,直接跳转到parameters_done即可
je     0x00007fdf4500079a 
// -- loop --
// 汇编执行到这里,说明paramter size不为0,需要传递参数
mov    -0x8(%rbp),%rdx
mov    %ecx,%esi
mov    (%rdx),%rax
add    $0x8,%rdx
dec    %esi
push   %rax
// 跳转到loop
jne    0x00007fdf4500078e  

因为要调用Java方法,所以会为Java方法压入实际的参数,也就是压入parameter size个从parameters开始取的参数。压入参数后的栈如下图所示。

当把需要调用Java方法的参数准备就绪后,接下来就会调用Java方法。这里需要重点提示一下Java解释执行时的方法调用约定,不像C/C++在x86下的调用约定一样,不需要通过寄存器来传递参数,而是通过栈来传递参数的,说的更直白一些,是通过局部变量表来传递参数的,所以上图CallStub()函数栈帧中的argument word1 ... argument word n其实是​被调用的Java方法局部变量表的一部分。

下面接着看调用Java方法的代码,如下:

// 调用Java方法
// -- parameters_done --

__ BIND(parameters_done); 
// 将method地址包含的数据接Method*拷贝到rbx中
__ movptr(rbx, method);            
// 将解释器的入口地址拷贝到c_rarg1寄存器中
__ movptr(c_rarg1, entry_point);    
// 将rsp寄存器的数据拷贝到r13寄存器中
__ mov(r13, rsp);                   

// 调用解释器的解释函数,从而调用Java方法
// 调用的时候传递c_rarg1,也就是解释器的入口地址
__ call(c_rarg1); 

生成的汇编代码如下:

// 将Method*送到%rbx中
mov     -0x18(%rbp),%rbx  
// 将entry_point送到%rsi中
mov     -0x10(%rbp),%rsi  
// 将调用者的栈顶指针保存到%r13中
mov     %rsp,%r13    
// 调用Java方法     
callq   *%rsi             

注意调用callq指令后,会将callq指令的下一条指令的地址压栈,再跳转到第1操作数指定的地址,也就是*%rsi表示的地址。压入下一条指令的地址是为了让函数能通过跳转到栈上的地址从子函数返回。 

callq指令调用的是entry_point。entry_point在后面会详细介绍。

公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



猜您在找 第29篇-调用Java主类的main()方法 java 主类的main方法调用其他方法 Java中是否可以调用一个类中的main方法? java main方法也可以被其他方法调用 4.编写Java应用程序。首先,定义一个时钟类——Clock,它包括三个int型 成员变量分别表示时、分、秒,一个构造方法用于对三个成员变量(时、分、秒) 进行初始化,还有一个成员方法show()用于显示时钟对象的时间。其次,再定义 一个主类——TestClass,在主类的main方法中创建多个时钟类的对象,使用这 些对象调用方法show()来显示时钟的时间。 编写一个类A,该类创建的对象可以调用方法f输出小写的英文字母表。然 后再编写一个A类的子类B,要求子类B必须继承类A的方法f(不允许重写), 子类B创建的对象不仅可以调用方法f输出小写的英文字母表,而且可以调用子 类新增的方法g输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。 编写Java应用程序。首先,定义描述学生的类——Student,包括学号(int)、 姓名(String)、年龄(int)等属性;二个方法:Student(int stuNo,String name,int age) 用于对对象的初始化,outPut()用于输出学生信息。其次,再定义一个主类—— TestClass,在主类的main方法中创建多个Student类的对象,使用这些对象来测 试Stud JAVA 主函数(主方法) 首先,定义一个时钟类——Clock,它包括三个int型 成员变量分别表示时、分、秒,一个构造方法用于对三个成员变量(时、分、秒) 进行初始化,还有一个成员方法show()用于显示时钟对象的时间。其次,再定义 一个主类——TestClass,在主类的main方法中创建多个时钟类的对象,使用这 些对象调用方法show()来显示时钟的时间。 IDEA创建JAVA项目常见问题:找不到或无法加载主类 Main的解决办法
 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM