在 第6篇-Java方法新栈帧的创建 介绍过局部变量表的创建,创建完成后的栈帧状态如下图所示。

各个寄存器的状态如下所示。

// %rax寄存器中存储的是返回地址
rax: return address
// 要执行的Java方法的指针
rbx: Method*
// 本地变量表指针
r14: pointer to locals
// 调用者的栈顶
r13: sender sp

注意rax中保存的返回地址,因为在generate_call_stub()函数中通过__ call(c_rarg1) 语句调用了由generate_normal_entry()函数生成的entry_point,所以当entry_point执行完成后,还会返回到generate_call_stub()函数中继续执行__ call(c_rarg1) 语句下面的代码,也就是

第5篇-调用Java方法后弹出栈帧及处理返回结果  涉及到的那些代码。

调用的generate_fixed_frame()函数的实现如下:

源代码位置:src/cpu/x86/vm/templateInterpreter_x86_64.cpp

void TemplateInterpreterGenerator::generate_fixed_frame(bool native_call) {
// 把返回地址紧接着局部变量区保存
__ push(rax);
// 为Java方法创建栈帧
__ enter();
// 保存调用者的栈顶地址
__ push(r13);
// 暂时将last_sp属性的值设置为NULL_WORD
__ push((int)NULL_WORD);
// 获取ConstMethod*并保存到r13中
__ movptr(r13, Address(rbx, Method::const_offset()));
// 保存Java方法字节码的地址到r13中
__ lea(r13, Address(r13, ConstMethod::codes_offset()));
// 保存Method*到堆栈上
__ push(rbx); // ProfileInterpreter属性的默认值为true,
// 表示需要对解释执行的方法进行相关信息的统计
if (ProfileInterpreter) {
Label method_data_continue;
// MethodData结构基础是ProfileData,
// 记录函数运行状态下的数据
// MethodData里面分为3个部分,
// 一个是函数类型等运行相关统计数据,
// 一个是参数类型运行相关统计数据,
// 还有一个是extra扩展区保存着
// deoptimization的相关信息
// 获取Method中的_method_data属性的值并保存到rdx中
__ movptr(rdx, Address(rbx,
in_bytes(Method::method_data_offset())));
__ testptr(rdx, rdx);
__ jcc(Assembler::zero, method_data_continue);
// 执行到这里,说明_method_data已经进行了初始化,
// 通过MethodData来获取_data属性的值并存储到rdx中
__ addptr(rdx, in_bytes(MethodData::data_offset()));
__ bind(method_data_continue);
__ push(rdx);
} else {
__ push(0);
} // 获取ConstMethod*存储到rdx
__ movptr(rdx, Address(rbx,
Method::const_offset()));
// 获取ConstantPool*存储到rdx
__ movptr(rdx, Address(rdx,
ConstMethod::constants_offset()));
// 获取ConstantPoolCache*并存储到rdx
__ movptr(rdx, Address(rdx,
ConstantPool::cache_offset_in_bytes()));
// 保存ConstantPoolCache*到堆栈上
__ push(rdx);
// 保存第1个参数的地址到堆栈上
__ push(r14); if (native_call) {
// native方法调用时,不需要保存Java
// 方法的字节码地址,因为没有字节码
__ push(0);
} else {
// 保存Java方法字节码地址到堆栈上,
// 注意上面对r13寄存器的值进行了更改
__ push(r13);
} // 预先保留一个slot,后面有大用处
__ push(0);
// 将栈底地址保存到这个slot上
__ movptr(Address(rsp, 0), rsp);
}

对于普通的Java方法来说,生成的汇编代码如下:  

push   %rax
push %rbp
mov %rsp,%rbp
push %r13
pushq $0x0
mov 0x10(%rbx),%r13
lea 0x30(%r13),%r13 // lea指令获取内存地址本身
push %rbx
mov 0x18(%rbx),%rdx
test %rdx,%rdx
je 0x00007fffed01b27d
add $0x90,%rdx
push %rdx
mov 0x10(%rbx),%rdx
mov 0x8(%rdx),%rdx
mov 0x18(%rdx),%rdx
push %rdx
push %r14
push %r13
pushq $0x0
mov %rsp,(%rsp)

汇编比较简单,这里不再多说。执行完如上的汇编后生成的栈帧状态如下图所示。

 

调用完generate_fixed_frame()函数后一些寄存器中保存的值如下:

rbx:Method*
ecx:invocation counter
r13:bcp(byte code pointer)
rdx:ConstantPool* 常量池的地址
r14:本地变量表第1个参数的地址

执行完generate_fixed_frame()函数后会继续返回执行InterpreterGenerator::generate_normal_entry()函数,如果是为同步方法生成机器码,那么还需要调用lock_method()函数,这个函数会改变当前栈帧的状态,添加同步所需要的一些信息,在后面介绍锁的实现时会详细介绍。

InterpreterGenerator::generate_normal_entry()函数最终会返回生成机器码的入口执行地址,然后通过变量_entry_table数组来保存,这样就可以使用方法类型做为数组下标获取对应的方法入口了。 

推荐阅读:

第1篇-关于JVM运行时,开篇说的简单些

第2篇-JVM虚拟机这样来调用Java主类的main()方法

第3篇-CallStub新栈帧的创建

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

第5篇-调用Java方法后弹出栈帧及处理返回结果

第6篇-Java方法新栈帧的创建

如果有问题可直接评论留言或加作者微信mazhimazh

关注公众号,有HotSpot源码剖析系列文章!

  

第7篇-为Java方法创建栈帧的更多相关文章

  1. 第6篇-Java方法新栈帧的创建

    在 第2篇-JVM虚拟机这样来调用Java主类的main()方法 介绍JavaCalls::call_helper()函数的实现时提到过如下一句代码: address entry_point = me ...

  2. 第5篇-调用Java方法后弹出栈帧及处理返回结果

    在前一篇 第4篇-JVM终于开始调用Java主类的main()方法啦 介绍了通过callq调用entry point,不过我们并没有看完generate_call_stub()函数的实现.接下来在ge ...

  3. Java虚拟机之栈帧

    写在前面的话:Java虚拟机是一门学问,是众多Java大神们的杰作,由于我个人水平有限,精力有限,不能保证所有的东西都是正确的,这里内容都是经过深思熟虑的,部分引用原著的内容,讲的已经很好了,不在累述 ...

  4. 详细解析Java虚拟机的栈帧结构

    欢迎关注微信公众号:万猫学社,每周一分享Java技术干货. 什么是栈帧? 正如大家所了解的,Java虚拟机的内存区域被划分为程序计数器.虚拟机栈.本地方法栈.堆和方法区.(什么?你还不知道,赶紧去看看 ...

  5. java方法创建

    一个方法public(作用域) void(void是不要返回值,String返回String类型,User(自定义的类型)返回User类型) test(方法名) (int a(参数)){ } stat ...

  6. 第29篇-调用Java主类的main()方法

    在第1篇中大概介绍过Java中主类方法main()的调用过程,这一篇介绍的详细一点,大概的调用过程如下图所示. 其中浅红色的函数由主线程执行,而另外的浅绿色部分由另外一个线程执行,这个线程最终也会负责 ...

  7. 第48篇-native方法调用解释执行的Java方法

    举一个native方法调用解释执行的Java方法的实例,如下: public class TestJNI { static { System.load("/media/mazhi/sourc ...

  8. JAVA方法调用中的解析与分派

    JAVA方法调用中的解析与分派 本文算是<深入理解JVM>的读书笔记,参考书中的相关代码示例,从字节码指令角度看看解析与分派的区别. 方法调用,其实就是要回答一个问题:JVM在执行一个方法 ...

  9. java方法执行流程解析

    Java程序运行时,必须经过编译和运行两个步骤.首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件.然后Java虚拟机将编译好的字节码文件加载到内存(这个过程被称为类加 ...

随机推荐

  1. 暑假自学java第七天

    1,Object类: 任何类的父类都是Object 任何子类的对象都可以赋值给父类的引用.任何类的所有实例都可以用Object来代替 (3条消息) java中的Object类_iqqcode-CSDN ...

  2. svn创建新分支报错:svn: E155015: Aborting commit: XXX remains in conflict

    用diea在对svn创建新分支的时候报错,错误为 svn: E155015: Aborting commit: XXX remains in conflict 百度和查阅资料后得知,此错误为分支被拉取 ...

  3. springCloud--admin监控使用

    Admin监控应用 Spring Boot提供的监控接口,例如:/health./info等等,实际上除了之前提到的信息,还有其他信息业需要监控:当前处于活跃状态的会话数量.当前应用的并发数.延迟以及 ...

  4. 南京大学计算机基础 X64函数调用和链接器原理和可重定位的文件.o

    一. 1.函数调用差别 X64的函数调用,和X86函数调用区别,在于参数的传递不一样了,X64的参数传递不在依靠栈来传,而是寄存器,不过还是具有局限性的 比如只能允许六个寄存器来传,分别是RDI,RS ...

  5. Linux | 文件编辑命令

    cat cat 命令是是一次性显示文件的所有内容 cat 是 concatenate 的缩写,表示:连接/串联 cat 文件名 可以看到,cat 命令是一次性显示出所有的文件内容,这就导致了,有的文件 ...

  6. 你觉得我的这段Java代码还有优化的空间吗?

    上周,因为要测试一个方法的在并发场景下的结果是不是符合预期,我写了一段单元测试的代码.写完之后截了个图发了一个朋友圈,很多人表示短短的几行代码,涉及到好几个知识点. 还有人给出了一些优化的建议.那么, ...

  7. Ubuntu20.4 bs4安装的正确姿势

    一.背景 公司一小伙子反馈在内网机器上通过代理,还是安装不了bs4:于是乎,作为菜鸡的我开始排查.一直认为是网络和代理问题,所以关注点一直放在网络和安装包上:在网上搜索到,主要是以下问题: 1)更新a ...

  8. C语言:易错题

    1. int x=y=z=0;//实际只声明了变量x,而变量y,z并没有声明.可以修改为:int x=0,y=0,z=0; 或int x,y,z; x=y=z=0; 2.int z=(x+y)++;/ ...

  9. C语言:3个数排序

    #include <stdio.h> int main() { int a,b,c,t; /*定义4个基本整型变量a.b.c.t*/ printf("Please input a ...

  10. python 分析文本文件

    def count_words(filename):#统计指定文件单词的数量 """Count the approximate number of words in a ...