第4篇-JVM终于开始调用Java主类的main()方法啦
在前一篇 第3篇-CallStub新栈帧的创建 中我们介绍了generate_call_stub()函数的部分实现,完成了向CallStub栈帧中压入参数的操作,此时的状态如下图所示。

继续看generate_call_stub()函数的实现,接来下会加载线程寄存器,代码如下:
// Load up thread register
__ movptr(r15_thread, thread);
__ reinit_heapbase();
生成的汇编代码如下:
mov 0x18(%rbp),%r15
mov 0x1764212b(%rip),%r12 # 0x00007fdf5c6428a8
对照着上面的栈帧可看一下0x18(%rbp)这个位置存储的是thread,将这个参数存储到%r15寄存器中。
如果在调用函数时有参数的话需要传递参数,代码如下:
// pass parameters if any
BLOCK_COMMENT("pass parameters if any"); // _masm-> block_comment("pass parameters if any")
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); // parameter pointer rdx
// 将c_rarg3中值拷贝到c_rarg1中,即将参数个数复制到c_rarg1中
__ movl(c_rarg1, c_rarg3); // parameter counter is in c_rarg1
__ BIND(loop);
// 将c_rarg2指向的内存中包含的地址复制到rax中
__ movptr(rax, Address(c_rarg2, 0));// get parameter
// c_rarg2中的参数对象的指针加上指针宽度8字节,即指向下一个参数
__ addptr(c_rarg2, wordSize); // advance to next parameter
// 将c_rarg1中的值减一
__ decrementl(c_rarg1); // decrement counter
// 传递方法调用参数
__ push(rax); // pass parameter
// 如果参数个数大于0则跳转到loop继续
__ jcc(Assembler::notZero, loop);
这里是个循环,用于传递参数,相当于如下代码:
while(%esi){
rax = *arg
push_arg(rax)
arg++; // ptr++
%esi--; // counter--
}
生成的汇编代码如下:
mov 0x10(%rbp),%ecx // 将栈中parameter size送到%ecx中
test %ecx,%ecx // 做与运算,只有当%ecx中的值为0时才等于0
je 0x00007fdf4500079a // 没有参数需要传递,直接跳转到parameters_done即可
// -- loop --
// 汇编执行到这里,说明paramter size不为0,需要传递参数
mov -0x8(%rbp),%rdx
mov %ecx,%esi
mov (%rdx),%rax
add $0x8,%rdx
dec %esi
push %rax
jne 0x00007fdf4500078e // 跳转到loop
因为要调用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); // bind(parameters_done); _masm-> block_comment("parameters_done" ":")
// 将method地址包含的数据接Method*拷贝到rbx中
__ movptr(rbx, method); // get Method*
// 将解释器的入口地址拷贝到c_rarg1寄存器中
__ movptr(c_rarg1, entry_point); // get entry_point
// 将rsp寄存器的数据拷贝到r13寄存器中
__ mov(r13, rsp); // set sender sp
BLOCK_COMMENT("call Java function"); // _masm-> block_comment("call Java function")
// 调用解释器的解释函数,从而调用Java方法
__ call(c_rarg1); // 调用的时候传递c_rarg1,也就是解释器的入口地址
生成的汇编代码如下:
mov -0x18(%rbp),%rbx // 将Method*送到%rbx中
mov -0x10(%rbp),%rsi // 将entry_point送到%rsi中
mov %rsp,%r13 // 将调用者的栈顶指针保存到%r13中
callq *%rsi // 调用Java方法
注意调用callq指令后,会将callq指令的下一条指令的地址压栈,再跳转到第1操作数指定的地址,也就是*%rsi表示的地址。压入下一条指令的地址是为了让函数能通过跳转到栈上的地址从子函数返回。
callq指令调用的是entry point。entry point在后面会详细介绍。
推荐阅读:
第2篇-JVM虚拟机这样来调用Java主类的main()方法
如果有问题可直接评论留言或加作者微信mazhimazh
关注公众号,有HotSpot源码剖析系列文章!

第4篇-JVM终于开始调用Java主类的main()方法啦的更多相关文章
- 第29篇-调用Java主类的main()方法
在第1篇中大概介绍过Java中主类方法main()的调用过程,这一篇介绍的详细一点,大概的调用过程如下图所示. 其中浅红色的函数由主线程执行,而另外的浅绿色部分由另外一个线程执行,这个线程最终也会负责 ...
- 第2篇-JVM虚拟机这样来调用Java主类的main()方法
在前一篇 第1篇-关于JVM运行时,开篇说的简单些 中介绍了call_static().call_virtual()等函数的作用,这些函数会调用JavaCalls::call()函数.我们看Java类 ...
- java 主类的main方法调用其他方法
方法1:A a=new test().new A(); 内部类对象通过外部类的实例对象调用其内部类构造方法产生,如下: public class test{ class A{ void fA(){ S ...
- 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。
22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...
- .编写Java应用程序。首先,定义一个Print类,它有一个方法void output(int x),如果x的值是1,在控制台打印出大写的英文字母表;如果x的值是2,在 控制台打印出小写的英文字母表。其次,再定义一个主类——TestClass,在主类 的main方法中创建Print类的对象,使用这个对象调用方法output ()来打印出大 小写英文字母表。
package com.homework.zw; //类Print部分 public class Print1 { int x; void output() { if(x==1) { for(int ...
- 编写Java应用程序。首先,定义一个Print类,它有一个方法void output(int x),如果x的值是1,在控制台打印出大写的英文字母表;如果x的值是2,在 控制台打印出小写的英文字母表。其次,再定义一个主类——TestClass,在主类 的main方法中创建Print类的对象,使用这个对象调用方法output ()来打印出大 小写英文字母表。
package zuoye; public class print1 { String a="abcdefghigklmnopqrstuvwxyz"; String B=" ...
- 编写Java应用程序。首先,定义一个时钟类——Clock,它包括三个int型 成员变量分别表示时、分、秒,一个构造方法用于对三个成员变量(时、分、秒) 进行初始化,还有一个成员方法show()用于显示时钟对象的时间。其次,再定义 一个主类——TestClass,在主类的main方法中创建多个时钟类的对象,使用这 些对象调用方法show()来显示时钟的时间。
package com.hanqi.test; public class Clock { int hh; int mm; int ss; String time; Clock(int h,int m, ...
- 按要求编写Java应用程序: (1)编写西游记人物类(XiYouJiRenWu) 其中属性有:身高(height),名字(name),武器(weapon) 方法有:显示名字(printName),显示武器(printWeapon) (2)在主类的main方法中创建二个对象:zhuBaJie,sunWuKong。并分别为他 们的两个属性(name,weapon)赋值,最后分别调用printName,
package com.hanqi.test; public class xiyoujirenwu { private double height;// 身高 private String name; ...
- 4.编写Java应用程序。首先,定义一个时钟类——Clock,它包括三个int型 成员变量分别表示时、分、秒,一个构造方法用于对三个成员变量(时、分、秒) 进行初始化,还有一个成员方法show()用于显示时钟对象的时间。其次,再定义 一个主类——TestClass,在主类的main方法中创建多个时钟类的对象,使用这 些对象调用方法show()来显示时钟的时间。
Clock package com.hanqi.test; public class Clock { int hour,minute,second; Clock(int h,int m,int s) ...
随机推荐
- 13、linux中用户和用户组
linux是多用户多进程的系统: 每个文件和进程都需要应对一个用户和用户组: linux系统通过uid和gid来识别用户和组的: 一个用户必须要有唯一的uid和一个主组来识别身份,不同的用户可以使用同 ...
- 聊一聊我在 B 站自学 Java 的经历吧
Hey guys ,这里是 cxuan,欢迎你收看我最新一期的文章. 这是一篇鸽了很久的文章...... 事情还要从上回说起...... 我爱 B 站! 这篇文章我汇总了 B 站上计算机基础(操作系统 ...
- Docker:Docker常用命令
docker信息 ##查看docker容器版本 docker version ##查看docker容器信息 docker info ##查看docker容器帮助 docker --help 镜像列表 ...
- 输出数组中出现次数最多且值最大的数字----python
class Solution(): #求最多的数 def find_max(self,list): num = 0 for i in list: print(i) if list.count(i) & ...
- Kubernetes全栈架构师(二进制高可用安装k8s集群部署篇)--学习笔记
目录 二进制高可用基本配置 二进制系统和内核升级 二进制基本组件安装 二进制生成证书详解 二进制高可用及etcd配置 二进制K8s组件配置 二进制使用Bootstrapping自动颁发证书 二进制No ...
- 『与善仁』Appium基础 — 1、Android系统的测试环境搭建
目录 1.Android操作系统简介 (1)Android系统诞生 (2)Android系统的结构 (3)Android测试环境搭建整体思路 2.Java环境安装 (1)JDK8下载 (2)安装JDK ...
- SDN开发环境搭建以及Mininet编程
一.实验内容 搭建如下网络拓扑,并熟悉相关指令. 二.搭建开发环境 2.1 开发环境搭建 2.1.1下载ubuntu镜像文件 镜像下载地址 https://www.ubuntu.com/downl ...
- 管理员的基本防范措施 Linux系统安全及应用
系统安全及应用一.账号安全基本措施① 系统账号清理② 密码安全控制③ 命令历史限制④ 终端自动注销二.SU命令切换用户① 用途及用法② 验证密码③ 限制使用su命令的用户④ 查看su操作记录补充三.L ...
- Spring Boot(一):如何使用Spring Boot搭建一个Web应用
Spring Boot Spring Boot 是Spring团队旗下的一款Web 应用框架 其优势可以更快速的搭建一个Web应用 从根本上上来讲 Spring Boot并不是什么新的框架技术 而是在 ...
- Leetcode4. 寻找两个正序数组的中位数
> 简洁易懂讲清原理,讲不清你来打我~ 输入两个递增数组,输出中位数![在这里插入图片描述](https://img-blog.csdnimg.cn/25550994642144228e9862 ...