Java字节码浅析(二)
英文原文链接,译文链接,原文作者:James Bloom,译者:有孚
条件语句
像if-else, switch这样的流程控制的条件语句,是通过用一条指令来进行两个值的比较,然后根据结果跳转到另一条字节码来实现的。
循环语句包括for循环,while循环,它们的实现方式也很类似,但有一点不同,它们通常都会包含一条goto指令,以便字节码实现循环执行。do-while循环不需要goto指令,因为它的条件分支是在字节码的末尾。更多细节请参考循环语句一节。
有一些指令可以用来比较两个整型或者两个引用,然后执行某个分支,这些操作都能在单条指令里面完成。而像double,float,long这些值需要两条指令。首先得去比较两个值,然后根据结果,会把1,0或者-1压到栈里。最后根据栈顶的值是大于,等于或者小于0来判断应该跳转到哪个分支。
我们先来介绍下if-else语句,然后再详细介绍下分支跳转用到的几种不同的指令。
if-else
下面的这个简单的例子是用来比较两个整数的:
1
|
public int greaterThen( int intOne, int intTwo) {
|
2
|
if (intOne > intTwo) {
|
3
|
return 0 ;
|
4
|
} else {
|
5
|
return 1 ;
|
6
|
}
|
7
|
}
|
方法最后会编译成如下的字节码:
1
|
0 : iload_1
|
2
|
1 : iload_2
|
3
|
2 : if_icmple 7
|
4
|
5 : iconst_0
|
5
|
6 : ireturn
|
6
|
7 : iconst_1
|
7
|
8 : ireturn
|
首先,通过iload_1, iload_2两条指令将两个入参压入操作数栈中。if_icmple会比较栈顶的两个值的大小。如果intOne小于或者等于intTwo的话,会跳转到第7行处的字节码来执行。可以看到这里和Java代码里的if语句的条件判断正好相反,这是因为在字节码里面,判断条件为真的话会跑到else分支里面去执行,而在Java代码里,判断为真会进入if块里面执行。换言之,if_icmple判断的是如果if条件不为真,然后跳过if块。if代码块里对应的代码是5,6处的字节码,而else块对应的是7,8处的。
下面的代码则稍微复杂了一点,它需要进行两次比较。
1
|
public int greaterThen( float floatOne, float floatTwo) {
|
2
|
int result;
|
3
|
if (floatOne > floatTwo) {
|
4
|
result = 1 ;
|
5
|
} else {
|
6
|
result = 2 ;
|
7
|
}
|
8
|
return result;
|
9
|
}
|
编译后会是这样:
01
|
0 : fload_1
|
02
|
1 : fload_2
|
03
|
2 : fcmpl
|
04
|
3 : ifle 11
|
05
|
6 : iconst_1
|
06
|
7 : istore_3
|
07
|
8 : goto 13
|
08
|
11 : iconst_2
|
09
|
12 : istore_3
|
10
|
13 : iload_3
|
11
|
14 : ireturn
|
在这个例子中,首先两个参数会被fload_1和fload_2指令压入栈中。和上面那个例子不同的是,这里需要比较两回。fcmple先用来比较栈顶的floatOne和floatTwo,然后把比较的结果压入操作数栈中。
1
|
* floatOne > floatTwo –> 1
|
2
|
* floatOne = floatTwo –> 0
|
3
|
* floatOne < floatTwo –> - 1
|
4
|
* floatOne or floatTwo = NaN –> 1
|
然后通过ifle进行判断,如果前面fcmpl的结果是< =0的话,则跳转到11行处的字节码去继续执行。
这个例子还有一个地方和前面不同的是,它只在方法末有一个return语句,因此在if代码块的最后,会有一个goto语句来跳过else块。goto语句会跳转到第13条字节码处,然后通过iload_3将存储在局部变量区第三个位置的结果压入栈中,然后就可以通过return指令将结果返回了。
除了比较数值的指令外,还有比较引用是否相等的(==),以及引用是否等于null的(== null或者!=null),以及比较对象的类型的(instanceof)。
if_icmp<cond> | 这组指令用来比较操作数栈顶的两个整数,然后跳转到新的位置去执行。<cond>可以是:eq-等于,ne-不等于,lt-小于,le-小于等于,gt-大于, ge-大于等于。 |
if_acmp<cond> | 这两个指令用来比较对象是否相等,然后根据操作数指定的位置进行跳转。 |
ifnonnull ifnull | 这两个指令用来判断对象是否为null,然后根据操作数指定的位置进行跳转。 |
lcmp | 这个指令用来比较栈顶的两个长整型,然后将结果值压入栈中: 如果value1>value2,压入1,如果value1==value2,压入0,如果value1<value2压入-1. |
fcmp<cond> l g dcomp<cond> | 这组指令用来比较两个float或者double类型的值,然后然后将结果值压入栈中:如果value1>value2,压入1,如果value1==value2,压入0,如果value1<value2压入-1. 指令可以以l或者g结尾,不同之处在于它们是如何处理NaN的。fcmpg和dcmpg指令把整数1压入操作数栈,而fcmpl和dcmpl把-1压入操作数栈。这确保了比较两个值的时候,如果其中一个不是数字(Not A Number, NaN),比较的结果不会相等。比如判断if x > y(x和y都是浮点数),就会用的fcmpl,如果其中一个值是NaN的话,-1会被压入栈顶,下一条指令则是ifle,如果分支小于0则跳转。因此如果有一个是NaN的话,ifle会跳过if块,不让它执行。 |
instanceof | 如果栈顶对象的类型是指定的类的话,则将1压入栈中。这个指令的操作数指定的是某个类型在常量池的序号。如果对象为空或者不是对应的类型,则将0压入操作数栈中。 |
if<cond> | 将栈顶值和0进行比较,如果条件为真,则跳转到指定的分支继续执行。这些指令通常用于较复杂的条件判断中,在一些单条指令无法完成的情况。比如验证方法调用的返回值。 |
switch语句
Java switch表达式的类型只能是char,byte,short,int,Character, Byte, Short,Integer,String或者enum。JVM为了支持switch语句,用了两个特殊的指令,叫做tableSwitch和lookupswitch,它们都只能操作整型数值。只能使用整型并不影响,因为char,byte,short和enum都可以提升成int类型。Java7开始支持String类型,下面我们会介绍到。tableswitch操作会比较快一些,不过它消耗的内存会更多。tableswitch会列出case分支里面最大值和最小值之间的所有值,如果判断的值不在这个范围内则直接跳转到default块执行,case中没有的值也会被列出,不过它们同样指向的是default块。拿下面的这个switch语句作为例子:
01
|
public int simpleSwitch( int intOne) {
|
02
|
switch (intOne) {
|
03
|
case 0 :
|
04
|
return 3 ;
|
05
|
case 1 :
|
06
|
return 2 ;
|
07
|
case 4 :
|
08
|
return 1 ;
|
09
|
default :
|
10
|
return - 1 ;
|
11
|
}
|
12
|
}
|
编译后会生成如下的字节码
01
|
0 : iload_1
|
02
|
1 : tableswitch {
|
03
|
default : 42
|
04
|
min: 0
|
05
|
max: 4
|
06
|
0 : 36
|
07
|
1 : 38
|
08
|
2 : 42
|
09
|
3 : 42
|
10
|
4 : 40
|
11
|
}
|
12
|
36 : iconst_3
|
13
|
37 : ireturn
|
14
|
38 : iconst_2
|
15
|
39 : ireturn
|
16
|
40 : iconst_1
|
17
|
41 : ireturn
|
18
|
42 : iconst_m1
|
19
|
43 : ireturn
|
tableswitch指令里0,1,4的值和代码里的case语句一一对应,它们指向的是对应代码块的字节码。tableswitch指令同样有2,3的值,但代码中并没有对应的case语句,它们指向的是default代码块。当这条指令执行的时候,会判断操作数栈顶的值是否在最大值和最小值之间。如果不在的话,直接跳去default分支,也就是上面的42行处的字节码。为了确保能找到default分支,它都是出现在tableswitch指令的第一个字节(如果需要内存对齐的话,则在补齐了之后的第一个字节)。如果栈顶的值在最大最小值的范围内,则用它作为tableswtich内部的索引,定位到应该跳转的分支。比如1的话,就会跳转至38行处继续执行。下图会演示这条指令是如何执行的:
如果case语句里面的值取值范围太广了(也就是太分散了)这个方法就不太好了,因为它占用的内存太多了。因此当switch的case条件里面的值比较分散的时候,就会使用lookupswitch指令。这个指令会列出case语句里的所有跳转的分支,但它没有列出所有可能的值。当执行这条指令的时候,栈顶的值会和lookupswitch里的每个值进行比较,来确定要跳转的分支。执行lookupswitch指令的时候,JVM会在列表中查找匹配的元素,这和tableswitch比起来要慢一些,因为tableswitch直接用索引就定位到正确的位置了。当switch语句编译的时候,编译器必须去权衡内存的使用和性能的影响,来决定到底该使用哪条指令。下面的代码,编译器会生成lookupswitch语句:
01
|
public int simpleSwitch( int intOne) {
|
02
|
switch (intOne) {
|
03
|
case 10 :
|
04
|
return 1 ;
|
05
|
case 20 :
|
06
|
return 2 ;
|
07
|
case 30 :
|
08
|
return 3 ;
|
09
|
default :
|
10
|
return - 1 ;
|
11
|
}
|
12
|
}
|
生成后的字节码如下:
01
|
0 : iload_1
|
02
|
1 : lookupswitch {
|
03
|
default : 42
|
04
|
count: 3
|
05
|
10 : 36
|
06
|
20 : 38
|
07
|
30 : 40
|
08
|
}
|
09
|
36 : iconst_1
|
10
|
37 : ireturn
|
11
|
38 : iconst_2
|
12
|
39 : ireturn
|
13
|
40 : iconst_3
|
14
|
41 : ireturn
|
15
|
42 : iconst_m1
|
16
|
43 : ireturn
|
为了确保搜索算法的高效(得比线性查找要快),这里会提供列表的长度,同时匹配的元素也是排好序的。下图演示了lookupswitch指令是如何执行的。
未完待续。
文章转自 并发编程网-ifeve.com
Java字节码浅析(二)相关推荐
- Java字节码技术(二)字节码增强之ASM、JavaAssist、Agent、Instrumentation
文章目录 前言 从AOP说起 静态代理 动态代理 JavaProxy CGLIB 字节码增强实现AOP ASM JavaAssist 运行时类加载 Instrumentation接口 JavaAgen ...
- Java字节码浅析(—)
英文原文链接,译文链接,原文作者:James Bloom,译者:有孚 明白Java代码是如何编译成字节码并在JVM上运行的非常重要,这有助于理解程序运行的时候究竟发生了些什么.理解这点不仅能搞清语言特 ...
- JVM 内部原理(七)— Java 字节码基础之二
JVM 内部原理(七)- Java 字节码基础之二 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...
- JAVA字节码指令iload_n为什么只有0到3?
点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 来源:r6d.c ...
- java 字节码增强原理_深入浅出Java探针技术1--基于java agent的字节码增强案例
Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一.什么是java agent? Java agent是在JDK1.5引入的,是一种可以动态修改 ...
- CGLIB依赖ASM(关于java字节码框架ASM的学习)
本文转自: http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html 一.什么是ASM ASM是一个java字节码操纵框架,它能被用来动态生 ...
- Java字节码深入解析
一:Java字节代码的组织形式 类文件{ OxCAFEBABE,小版本号,大版本号,常量池大小,常量池数组,访问控制标记,当前类信息,父类信息,实现的接口个数,实现的接口信息数组,域个数,域信息数组, ...
- 这一次,彻底弄懂 Java 字节码文件!
作者 | 东升的思考 责编 | Elle 不啰嗦,直接从最最简单的一段Java源代码开启Java整体字节码分析之旅. Java 源码文件 package com.dskj.jvm.bytecode; ...
- Java字节码角度分析多态原理 ——提升硬实力8
在前面的文章中,有详细地介绍java字节码相关的知识,有兴趣的可以提前了解一下. 1.Java字节码的一段旅行经历--提升硬实力1 2.Java字节码角度分析a++ --提升硬实力2 3.Java字节 ...
- Java字节码角度分析:Synchronized ——提升硬实力11
在前面的文章中,有详细地介绍java字节码相关的知识,有兴趣的可以提前了解一下. 1.Java字节码的一段旅行经历--提升硬实力1 2.Java字节码角度分析a++ --提升硬实力2 3.Java字节 ...
最新文章
- hiho_1139_二分+bfs搜索
- 新发现为类脑计算机开辟了道路
- 【转】GigE Vision简介
- r720支持多少频率的内存吗_电脑基础知识:内存条知识大全,看完小学生都了解...
- Spring BPP中优雅的创建动态代理Bean 1
- 屏蔽预训练模型的权重。 只训练最后一层的全连接的权重。_轻量化 | 如何让笨重的深度学习模型在移动设备上跑起来?看它!...
- 4.Node.js 微信消息管理
- 让元素固定_49种元素对钢铁性能的影响
- 用JavaScript获取输入的特殊字符
- (15)数据结构-平衡二叉树(AVL)
- 代码审查工具 phabricator 使用学习
- JavaScript锅打灰太狼游戏
- 实现拖拉机发牌程序——控制台版python
- VScode如何自动换行
- android切换夜间模式吗,Android切换夜间模式
- 启动VMware时遇到“vmx86版本不匹配问题”处理方法
- 抽象基类与接口,共性与个性的选择
- 测试sd卡读写速度与判断是否是扩容的假货
- 亚马逊正在逐渐压垮出版社,帮了世界一把
- web页面内调取QQ应用