第36篇-return字节码指令
方法返回的字节码相关指令如下表所示。
0xac |
ireturn |
从当前方法返回int |
0xad |
lreturn |
从当前方法返回long |
0xae |
freturn |
从当前方法返回float |
0xaf |
dreturn |
从当前方法返回double |
0xb0 |
areturn |
从当前方法返回对象引用 |
0xb1 |
return |
从当前方法返回void |
模板定义如下:
def(Bytecodes::_ireturn , ____|disp|clvm|____, itos, itos, _return , itos );
def(Bytecodes::_lreturn , ____|disp|clvm|____, ltos, ltos, _return , ltos );
def(Bytecodes::_freturn , ____|disp|clvm|____, ftos, ftos, _return , ftos );
def(Bytecodes::_dreturn , ____|disp|clvm|____, dtos, dtos, _return , dtos );
def(Bytecodes::_areturn , ____|disp|clvm|____, atos, atos, _return , atos );
def(Bytecodes::_return , ____|disp|clvm|____, vtos, vtos, _return , vtos ); def(Bytecodes::_return_register_finalizer , ____|disp|clvm|____, vtos, vtos, _return , vtos );
生成函数都为TemplateTable::_return()。但是如果是Object对象的构造方法中的return指令,那么这个指令还可能会被重写为_return_register_finalizer指令。
生成的return字节码指令对应的汇编代码如下:
第1部分:
// 将JavaThread::do_not_unlock_if_synchronized属性存储到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized属性值为false
0x00007fffe101b777: movb $0x0,0x2ad(%r15) // 将Method*加载到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将Method::_access_flags加载到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 检查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳转到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970 // 如果在%dl寄存器中存储的_do_not_unlock_if_synchronized的值不为0,
// 则跳转到no_unlock,表示不要释放和锁相关的资源
0x00007fffe101b792: test $0xff,%dl
0x00007fffe101b795: jne
0x00007fffe101ba90 // 跳转到----no_unlock----处
在JavaThread类中定义了一个属性_do_not_unlock_if_synchronized,这个值表示在抛出异常的情况下不要释放receiver(在非静态方法调用的情况下,我们总是会将方法解析到某个对象上,这个对象就是这里的receiver,也可称为接收者),此值仅在解释执行的情况下才会起作用。初始的时候会初始化为false。在如上汇编中可以看到,当_do_not_unlock_if_synchronized的值为true时,表示不需要释放receiver,所以虽然当前是同步方法,但是却直接调用到了no_unlock处。
在InterpreterGenerator::generate_native_entry()函数和InterpreterGenerator::generate_normal_entry()函数中会将当前线程的do_not_unlock_if_synchronized设置为true,然后执行increment invocation count & check for overflow的逻辑,最后会将do_not_unlock_if_synchronized属性的值设置为false。
有如下的类定义:
class UnlockFlagSaver {
private:
JavaThread* _thread;
bool _do_not_unlock;
public:
UnlockFlagSaver(JavaThread* t) {
_thread = t;
_do_not_unlock = t->do_not_unlock_if_synchronized();
t->set_do_not_unlock_if_synchronized(false);
}
~UnlockFlagSaver() {
_thread->set_do_not_unlock_if_synchronized(_do_not_unlock);
}
};
如果使用这个类,则JavaThread::_do_not_unlock_if_synchronized的值设置为false,完成后就恢复为以前的值。
在InterpreterRuntime::profile_method()函数中使用了UnlockFlagSaver,如下:
// use UnlockFlagSaver to clear and restore the _do_not_unlock_if_synchronized
// flag, in case 如果 this method triggers classloading which will call into Java.
UnlockFlagSaver fs(thread);
在InterpreterRuntime::exception_handler_for_exception()函数中,当do_not_unlock_if_synchronized的值为true时,会返回TemplateInterpreter::_remove_activation_entry。
只有TemplateInterpreterGenerator::generate_throw_exception() 函数才会调用InterpreterRuntime::exception_handler_for_exception()函数,生成的汇编由Interpreter::_rethrow_exception_entry和Interpreter::_throw_exception_entry等属性值保存。
第2部分:
如果执行如下汇编代码,则表示%dl寄存器中存储的_do_not_unlock_if_synchronized的值为0,需要执行释放锁的操作。
// 将之前字节码指令执行的结果存储到表达式栈顶,
// 由于return不需要返回执行结果,所以不需要设置返回值等信息,
// 最终在这里没有生成任何push指令 // 将BasicObjectLock存储到%rsi中,由于%rsi在调用C++函数时可做为
// 第2个参数传递,所以如果要调用unlock_object就可以传递此值
0x00007fffe101b79b: lea -0x50(%rbp),%rsi // 获取BasicObjectLock::obj属性地址存储到%rax中
0x00007fffe101b79f: mov 0x8(%rsi),%rax // 如果不为0,则跳转到unlock处,因为不为0,表示
// 这个obj有指向的锁对象,需要进行释放锁的操作
0x00007fffe101b7a3: test %rax,%rax
0x00007fffe101b7a6: jne 0x00007fffe101b8a8 // 跳转到----unlock----处 // 如果是其它的return指令,则由于之前通过push指令将结果保存在
// 表达式栈上,所以现在可通过pop将表达式栈上的结果弹出到对应寄存器中
第1个指令的-0x50(%rbp)指向了第1个BasicObjectLock对象,其中的sizeof(BasicObjectLock)的值为16,也就是16个字节。在之前我们介绍栈帧的时候介绍过Java解释栈的结构,如下:
假设当前的栈帧中有2个锁对象,则会在栈帧中存储2个BasicObjectLock对象,BasicObjectLock中有2个属性,_lock和_obj,分别占用8字节。布局如下图所示。
由于return字节码指令负责要释放的是加synchronized关键字的、解释执行的Java方法,所以为synchronized关键字建立的第1个锁对象存储在离当前栈帧最靠近栈底的地方,也就是上图中灰色部分,而其它锁对象我们暂时不用管。灰色部分表示的BasicObjectLock的地址通过-0x50(%rbp)就能获取到,然后对其中的_lock和_obj属性进行操作。
由于现在还没有介绍锁相关的知识,所以这里不做过多介绍,在后面介绍完锁相关知识后还会详细介绍。
第3部分:
在变量throw_monitor_exception为true的情况下,通过调用call_VM()函数生成抛出锁状态异常的汇编代码,这些汇编代码主要是为了执行C++函数InterpreterRuntime::throw_illegal_monitor_state_exception()。完成执行后还会执行由should_not_reach_here()函数生成的汇编代码。
在变量throw_monitor_exception为false并且install_monitor_exception为true的情况下,通过调用call_VM()函数生成汇编代码来执行C++函数InterpreterRuntime::new_illegal_monitor_state_exception()。最后跳转到unlocked处执行。
第4部分:
在InterpreterMacroAssembler::remove_activation()函数中,bind完unlock后就会调用InterpreterMacroAssembler::unlock_object()函数生成如下的汇编代码。InterpreterMacroAssembler::unlock_object()函数的作用如下:
Unlocks an object. Used in monitorexit bytecode and remove_activation. Throws an IllegalMonitorException if object is not locked by current thread.
生成的汇编代码如下:
// **** unlock **** // ============调用InterpreterMacroAssembler::unlock_object()函数生成如下的汇编代码================== // 将%r13存储到栈中,防止异常破坏了%r13寄存器中的值
0x00007fffe101b8a8: mov %r13,-0x38(%rbp) // 将BasicObjectLock::_lock的地址存储到%rax寄存器中
0x00007fffe101b8ac: lea (%rsi),%rax
// 将BasicObjectLock::_obj存储到%rcx寄存器中
0x00007fffe101b8af: mov 0x8(%rsi),%rcx // 将BasicObjectLock::_obj的值设置为NULL,表示释放锁操作
0x00007fffe101b8b3: movq $0x0,0x8(%rsi) // ----------当UseBiasedLocking的值为true时,调用MacroAssembler::biased_locking_exit()生成如下的汇编代码------------
// 从BasicObjectLock::_obj对象中取出mark属性值并相与
0x00007fffe101b8bb: mov (%rcx),%rdx
0x00007fffe101b8be: and $0x7,%rdx
// 如果BasicObjectLock::_obj指向的oop的mark属性后3位是偏向锁的状态,则跳转到---- done ----
0x00007fffe101b8c2: cmp $0x5,%rdx
0x00007fffe101b8c6: je 0x00007fffe101b96c
// ------------------------结束调用MacroAssembler::biased_locking_exit()生成的汇编代码--------------------- // 将BasicObjectLock::_lock这个oop对象的_displaced_header属性值取出
0x00007fffe101b8cc: mov (%rax),%rdx
// 判断一下是否为锁的重入,如果是锁的重入,则跳转到---- done ----
0x00007fffe101b8cf: test %rdx,%rdx
0x00007fffe101b8d2: je 0x00007fffe101b96c // 让BasicObjectLock::_obj的那个oop的mark恢复为
// BasicObjectLock::_lock中保存的原对象头
0x00007fffe101b8d8: lock cmpxchg %rdx,(%rcx)
// 如果为0,则表示锁的重入,跳转到---- done ---- ????
0x00007fffe101b8dd: je 0x00007fffe101b96c // 让BasicObjectLock::_obj指向oop,这个oop的对象头已经替换为了BasicObjectLock::_lock中保存的对象头
0x00007fffe101b8e3: mov %rcx,0x8(%rsi) // -----------调用call_VM()函数生成汇编代码来执行C++函数InterpreterRuntime::monitorexit()----------------
0x00007fffe101b8e7: callq 0x00007fffe101b8f1
0x00007fffe101b8ec: jmpq 0x00007fffe101b96c
0x00007fffe101b8f1: lea 0x8(%rsp),%rax
0x00007fffe101b8f6: mov %r13,-0x38(%rbp)
0x00007fffe101b8fa: mov %r15,%rdi
0x00007fffe101b8fd: mov %rbp,0x200(%r15)
0x00007fffe101b904: mov %rax,0x1f0(%r15)
0x00007fffe101b90b: test $0xf,%esp
0x00007fffe101b911: je 0x00007fffe101b929
0x00007fffe101b917: sub $0x8,%rsp
0x00007fffe101b91b: callq 0x00007ffff66b3d22
0x00007fffe101b920: add $0x8,%rsp
0x00007fffe101b924: jmpq 0x00007fffe101b92e
0x00007fffe101b929: callq 0x00007ffff66b3d22
0x00007fffe101b92e: movabs $0x0,%r10
0x00007fffe101b938: mov %r10,0x1f0(%r15)
0x00007fffe101b93f: movabs $0x0,%r10
0x00007fffe101b949: mov %r10,0x200(%r15)
0x00007fffe101b950: cmpq $0x0,0x8(%r15)
0x00007fffe101b958: je 0x00007fffe101b963
0x00007fffe101b95e: jmpq 0x00007fffe1000420
0x00007fffe101b963: mov -0x38(%rbp),%r13
0x00007fffe101b967: mov -0x30(%rbp),%r14
0x00007fffe101b96b: retq
// ------------------------结束call_VM()函数调用生成的汇编代码-------------------------------- // **** done **** 0x00007fffe101b96c: mov -0x38(%rbp),%r13
0x00007fffe101b970: mov -0x40(%rbp),%rsi // ==========结束调用InterpreterMacroAssembler::unlock_object()函数生成如下的汇编代码============
第5部分:
// 如果是其它的return指令,则由于之前通过push指令将结果保存在
// 表达式栈上,所以现在可通过pop将表达式栈上的结果弹出到对应寄存器中 // **** unlocked ****
// 在执行这里的代码时,表示当前的栈中没有相关的锁,也就是
// 相关的锁对象已经全部释放 // **** restart ****
// 检查一下,是否所有的锁都已经释放了 // %rsi指向当前栈中最靠栈顶的BasicObjectLock
0x00007fffe101b970: mov -0x40(%rbp),%rsi
// %rbx指向当前栈中最靠栈底的BasicObjectLock
0x00007fffe101b974: lea -0x40(%rbp),%rbx // 跳转到----entry----
0x00007fffe101b978: jmpq 0x00007fffe101ba8b
第6部分:
执行如下代码,会通过调用call_VM()函数来生成调用InterpreterRuntime::throw_illegal_monitor_state_exception()函数的代码:
// **** exception ****
// Entry already locked, need to throw exception // 当throw_monitor_exception的值为true时,执行如下2个函数生成的汇编代码:
// 执行call_VM()函数生成的汇编代码,就是调用C++函数InterpreterRuntime::throw_illegal_monitor_state_exception()
// 执行should_not_reach_here()函数生成的汇编代码 // 当throw_monitor_exception的值为false,执行如下汇编:
// 执行调用InterpreterMacroAssembler::unlock_object()函数生成的汇编代码
// install_monitor_exception的值为true时,执行call_VM()函数生成的汇编代码,就是调用C++函数InterpreterRuntime::new_illegal_monitor_state_exception()
// 无条件跳转到----restart ----
第7部分:
// **** loop **** // 将BasicObjectLock::obj与NULL比较,如果不相等,则跳转到----exception----
0x00007fffe101ba79: cmpq $0x0,0x8(%rsi)
0x00007fffe101ba81: jne 0x00007fffe101b97d // 则跳转到----exception----
第8部分:
// **** entry **** // 0x10为BasicObjectLock,找到下一个BasicObjectLock
0x00007fffe101ba87: add $0x10,%rsi
// 检查是否到达了锁对象存储区域的底部
0x00007fffe101ba8b: cmp %rbx,%rsi
// 如果不相等,跳转到loop
0x00007fffe101ba8e: jne 0x00007fffe101ba79 // 跳转到----loop----
第9部分:
// **** no_unlock **** // 省略jvmti support // 将-0x8(%rbp)处保存的old stack pointer(saved rsp)取出来放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除栈帧
// leave指令相当于:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 将返回地址弹出到%r13中
0x00007fffe101bacc: pop %r13
// 设置%rsp为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
其中的解释方法返回地址为return address,由于当前是C++函数调用Java,所以这个返回地址其实是C++函数的返回地址,我们不需要考虑。
整个的调用转换如下图所示。
其中的红色部分表示终结这个流程。
在return字节码指令中会涉及到锁释放的流程,所以上面的流程图看起来会复杂一些,等我们介绍完锁相关知识后会再次介绍return指令,这里不再过多介绍。
公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信,拉你入虚拟机群交流
第36篇-return字节码指令的更多相关文章
- 第32篇-解析interfacevirtual字节码指令
在前面介绍invokevirtual指令时,如果判断出ConstantPoolCacheEntry中的_indices字段的_f2属性的值为空,则认为调用的目标方法没有连接,也就是没有向Constan ...
- 第34篇-解析invokeinterface字节码指令
与invokevirtual指令类似,当没有对目标方法进行解析时,需要调用LinkResolver::resolve_invoke()函数进行解析,这个函数会调用其它一些函数完成方法的解析,如下图所示 ...
- invokedynamic字节码指令
1. 方法引用和invokedynamic invokedynamic是jvm指令集里面最复杂的一条.本文将从高观点的角度下分析invokedynamic指令是如何实现方法引用(Method refe ...
- [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式
前言简介 前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是 ...
- 大话+图说:Java字节码指令——只为让你懂
前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...
- 从字节码指令看重写在JVM中的实现
Java是解释执行的.包含动态链接的特性.都给解析或执行期间提供了非常多灵活扩展的空间.面向对象语言的继承.封装和多态的特性,在JVM中是怎样进行编译.解析,以及通过字节码指令怎样确定方法调用的版本号 ...
- java虚拟机(十四)--字节码指令
字节码指令其实是很重要的,在之前学习String等内容,深入到字节码层面很容易找到答案,而不是只是在网上寻找答案,还有可能是错误的. PS:本文基于jdk1.8 首先写个简单的类: public cl ...
- 硬核万字长文,深入理解 Java 字节码指令(建议收藏)
Java 字节码指令是 JVM 体系中非常难啃的一块硬骨头,我估计有些读者会有这样的疑惑,"Java 字节码难学吗?我能不能学会啊?" 讲良心话,不是我谦虚,一开始学 Java 字 ...
- 【JVM源码解析】模板解释器解释执行Java字节码指令(上)
本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...
随机推荐
- supermvc介绍
马上要开始写毕设了,需要一个合适的框架.想想自己用过的几个框框speedphp tp啊 还有公司的 dagger啊 ,大同小易.每一种都有自己喜欢的地方.然后想到了二八理论,我们常用的功能可能不到框架 ...
- Python爬虫之PySpider框架
概述 pyspider 是一个支持任务监控.项目管理.多种数据库,具有 WebUI 的爬虫框架,它采用 Python 语言编写,分布式架构.详细特性如下: 拥有 Web 脚本编辑界面,任务监控器,项目 ...
- HTML 网页开发、CSS 基础语法—— 一. HTML概述(了解网页)
1. 网页的本质 ① HTML就是用来制作网页文件的. ② 浏览器查看的网页都是.html或.htm文件. ③ HTML叫做超文本标记语言(Hypertext Markup Language),用于搭 ...
- 从零开始部署 Yapi(Windows+Nginx)
一.环境准备及安装 本文中是以本地 Windows 作为安装环境,Nginx 做反向代理,亲测验证可用. Yapi 运行需要的环境: Nodejs,MongoDB 安装包都在文档末尾处 1.1 安装 ...
- 我的Python学习记录
Python日期时间处理:time模块.datetime模块 Python提供了两个标准日期时间处理模块:--time.datetime模块. 那么,这两个模块的功能有什么相同和共同之处呢? 一般来说 ...
- 解决 Delegate IDE build/run actions to Maven 编译两次的问题
起因:我的电脑炸了,吸取教训,以后重要的东西千万不要存在C盘,特别是我们 IT 行业的,代码和文档都是一点一点积累的经验.突然没了,总感觉少了点啥,平时我的代码都是放在D盘,但是很多文档放在C盘,导致 ...
- 前端VUE基于gitlab的CI_CD
目录 CI 1.Gitlab的CI 1.1 GitLab-Runner 1.2 .gitlab-ci.yml 1.3 配置.gitlab-ci.yml 1.3.1 Pipeline概念 1.3.2 S ...
- JDK源码阅读(4):HashMap类阅读笔记
HashMap public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, ...
- 基于python的pixiv爬虫
基于python的pixiv爬虫 1.目标 在和朋友吹逼过程中,聊到qq群机器人,突发奇想动手做一个p站每日推荐色图的色图机,遂学习爬虫. 目标: 批量下载首页推荐色图. 由于对qq机器人不熟,先利用 ...
- 深入思考软件工程,开启 DevOps 之旅
20 世纪 60 年代,软件开始脱离硬件,逐渐成为一个独立产业.至今,软件开发过程从瀑布模型.CMM/CMMI,到 20 年前敏捷的诞生,再到今天 DevOps 的火热,一代代软件人在思考和探索,如何 ...