java虚拟机编译_[四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式...
前言简介
前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明
想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是如何编译你的代码的
本文不是从最底层的编译原理讲解
本文是针对java代码,去查看归纳总结编译器的结果行为,从而直观的感受到字节码指令集
也就是说本文的内容,主要针对的是使用javap 查看字节码文件中方法的code属性中的字节码内容
让你从java代码 class文件格式,以及字节码指令集 进行一个直观的演示
提醒:
如果你对字节码指令不了解,而且,没有看过前面的文章,本文可能会轻度不适.
本文示例只是为了展示
您应该经常查看你自己的代码的class文件去发现其中的规律
一条普通的指令格式
[ [ ... ]] []
index 表示偏移量 行号 等
opcode 表示操作码
operandX表示操作数
comment 为注释
比如下图所示
行号
0 , 操作码 getstatic ,操作数 #24 注释为 Field
java/lang/System..................
其中 index 行号/偏移量 可以作为控制跳转指令的跳转目标 比如 goto 8
表示跳转到索引为8的指令上
还有一点需要注意的是,javap查看到的内容,你可以认为是class文件表述的信息,但是绝不能理解为就是class文件中的内容
比如,class文件中没有操作码的助记符,比如,getstatic
,都是指令的二进制值
再比如刚才说到的,跳转到指定行号,对于控制转移指令,实际的操作数是在当前指令的操作码集合中的地址偏移量
并不是那个8
只不过javap工具按照更适合我们阅读的方式进行了翻译
加载存储与算数指令
public static voidmain(String[] args) {int i = -1;int j = 3;int k = 5;int l = 127;int m = 32767;int n = 32768;int add = i+j;int sub = i-j;int mul = j*k;int div = j/k;int rem = k%j;int neg = ~j;int inc = i++;
}
-1 ~ 5
使用const加载到操作数栈 其中-1 使用iconst_m1
-128~127 使用bipush
-32768~32767使用sipush
其余常量池ldc
store从操作数栈保存到局部变量表
load加载局部变量到操作数栈
0. 常量-1 加载到操作数栈
1. 操作数栈保存到1号局部变量表 也就是 i = -1;
2. 常量 3 加载到操作数栈
3. 操作数栈保存到2号局部变量表 也就是j = 3;
4. 常量 5
加载到操作数栈
5. 操作数栈保存到3号局部变量表 也就是k =5;
6. 常量 127
加载到操作数栈
8. 操作数栈保存到4号局部变量表 也就是l = 127;
10.常量 32767 加载到操作数栈
13.操作数栈保存到5号局部变量表 也就是m =
32767;
15.加载#17号常量池数据到操作数栈
17. 操作数栈保存到6号局部变量表 也就是n = 32768;
19. 加载1号局部变量到操作数栈 对应 i
20.
加载2号局部变量到操作数栈 对应 j
21. 执行iadd指令计算并将结果压入栈顶 对应 i+j;
22.
保存栈顶元素到7号局部变量
24. 加载1号局部变量到操作数栈 对应 i
25. 加载2号局部变量到操作数栈 对应
j
26.执行isub指令计算并将结果压入栈顶 对应i-j;
27. 保存栈顶元素减法结果到8号局部变量
29,30
加载 2号和3号局部变量到操作数栈 也就是j k
31 执行imul指令并将结果压栈 j*k
32
保存栈顶元素乘法结果到9号局部变量
34.35 加载 2号和3号局部变量到操作数栈 也就是j k
36 执行idiv
结果压入栈顶
37保存idiv结果到10号局部变量
39.40 加载3号 和 2号 也就是k j
41 执行求余irem
结果压入栈顶
42 栈顶元素结果保存到11号局部变量
44加载2号局部变量 对应 j 到操作数栈
45
加载常量-1到操作数栈
46 执行异或运算结果压入栈顶 (~x = -1 ^ x;)
47栈顶结果保存到12号局部变量
49
加载1号局部变量 对应 i
50 执行增量 1 计算 结果压入栈顶
53 栈顶结果保存到13号变量
55 void方法
return返回
类型转换指令
public static voidmain(String[] args) {boolean bNum = true;char cNum = 2;byte byteNum = 127;short sNum = 32767;int iNum = 100;long lNum = 65536;float fNum = 2.5f;double dNum = 6.8;char c1 = (char)byteNum;char c2 = (char)sNum;char c3 = (char)iNum;char c4 = (char)lNum;char c5 = (char)fNum;char c6 = (char)dNum;byte b1 = (byte)cNum;byte b2 = (byte)sNum;byte b3 = (byte)iNum;byte b4 = (byte)lNum;byte b5 = (byte)fNum;byte b6 = (byte)dNum;short s1 = (short)cNum;short s2 = (short)byteNum;short s3 = (short)iNum;short s4 = (short)lNum;short s5 = (short)fNum;short s6 = (short)dNum;int i1 = (int)cNum;int i2 = (int)byteNum;int i3 = (int)sNum;int i4 = (int)lNum;int i5 = (int)fNum;int i6 = (int)dNum;long l1 = (long)byteNum;long l2 = (long)cNum;long l3 = (long)sNum;long l4 = (long)iNum;long l5 = (long)fNum;long l6 = (long)dNum;float f1 = (float)byteNum;float f2 = (float)cNum;float f3 = (float)sNum;float f4 = (float)iNum;float f5 = (float)lNum;float f6 = (float)dNum;double d1 = (double)byteNum;double d2 = (double)cNum;double d3 = (double)sNum;double d4 = (double)iNum;double d5 = (double)lNum;double d6 = (double)fNum;
}
javap解析后的内容太长,接下来分段解析
数据的加载与存储
从数据的存储可以看得出来
boolean内部使用的是数值1 也就是1 表示true
数据类型转换为char类型
char byte short int 内部形式均为int 所以转换为char是,使用的全都是 i2c
long float double 先转换为int(l2i f2i d2i) 然后在统一使用
i2c 转换为char
数据类型转换为byte 类型
char byte short int 内部形式均为int 所以转换为byte时,使用的全都是 i2b
long float double 先转换为int(l2i f2i
d2i) 然后在统一使用 i2b 转换为 byte
数据类型转换为short 类型
还是同样的道理,char byte short int 内部形式均为int 所以转换为short
使用的是 i2s
long float double 先转换为int(l2i f2i
d2i) 然后在统一使用 i2s 转换为 short
数据类型转换为int 类型
char byte
short内部都是int类型.将他们转换为int时,不需要进行转换
如下图所示,一个load 对应一个store
long float double (l2i
f2i d2i) 转换为int
数据类型转换为long 类型
char byte short int 内部都是int类型.将他们转换为long
时,使用 i2l
float double 转换为long f2l d2l
数据类型转换为float 类型
char byte short int 内部都是int类型.将他们转换为float 时,使用 i2f
long double 转换为float l2f d2f
数据类型转换为double 类型
char byte short int 内部都是int类型.将他们转换为double 时,使用 i2d
long
float 转换为double l2d f2d
类相关指令
classSuper{
}class Sub extendsSuper{
}newObject();newSuper();
Super s= newSuper();new Double(1.5);newSub();
Sub sub= new Sub();
new Object();
new Super();
没有赋值给局部变量 仅仅是创建对象 调用new之后,堆中对象的引用保存在栈顶
然后调用构造方法invokespecial
Super s = new Super();
同上面的,需要调用new
因为还需要保存到局部变量
所以new之后 先copy一个,也就是dup
然后调用构造方法 invokespecial
然后从操作数栈保存到局部变量 store
Super super1 = newSuper();
Sub sub= newSub();//父类引用可以指向子类//子类引用不能指向父类//但是对于指向子类的父类引用 可以通过类型转换为子类Super subToSuper=sub;
Sub superToSub= (Sub) subToSuper;
0
创建Spper
3 复制
4 调用构造方法
7 保存到1号局部变量
8 创建Sub
11 复制
12调用构造方法
15
保存到2号局部变量
16 2号加载到操作数栈
17保存到3号局部变量
18加载3号局部变量到栈
19 checkcast
进行校验确认是否可以转换为指定类型 否则报错抛 classCastException
22 再次保存到局部变量
控制转移指令
voidintWhile() {int i = 0;while (i < 100) {
i++;
}
}voidintDoWhile() {int i = 0;do{
i++;
}while (i < 100);
}voidintFor() {int j = 0;for(int i =0;i<100;i++) {
j++;
}
}
intWhile()方法
0.
加载常量0 到操作数栈
1.保存操作数栈元素到1号局部变量 i= 0;
2.直接跳转到第8行
8.1号局部变量加载到操作数栈 也就是i
作为第一个元素
9.加载常量100到操作数栈 也就是100作为第二个元素
11.比较大小,如果前者小于后者 也就是如果 i <100 满足
跳转到第5行 否则顺序执行到14 return
5.给1号局部变量以增量1 增加
然后
8-->9-->11-->5-->8-->9-->11......往复循环 直到条件不满足,从11 跳转到14
结束
intDoWhile()
0.加载常量0到操作数栈
1.保存常量0
到1号局部变量
2.给1号局部变量以增量1 进行自增
5.1号局部变量加载到操作数栈
6.常量100加载到操作数栈
8,比较大小
如果前者小于后者也就是 1号局部变量 i<100 跳转到第2行
然后进行往复循环,直到条件不满足,然后顺序到return
intFor()
0. 加载常量0 到操作数栈
1. 保存栈顶元素到1号局部变量 j=0;
2. 加载常量0到操作数栈
3. 保存栈顶元素到2号局部变量i=0;
4. 跳转到13行
13. 加载2号局部变量到操作数栈
14. 加载常量100到操作数栈
16. 比较大小,如果前者 2号局部变量 i <100
跳转到7
7. 1号局部变量以增量1 自增 j++
10. 2号局部变量以增量1 自增
i++
13. 2号局部变量加载到操作数栈
14. 加载常量100到操作数栈
16. 比较大小,如果前者 2号局部变量 i <100 跳转到7
往复循环 如果条件不满足 从16 顺序到19 结束方法 return
public voidfun() {int i = 0;if(i<2) {
i++;
}else{
i--;
}
}
0, 加载常量0
到栈顶
1,保存栈顶元素 (0) 到1号局部变量
2. 加载1号局部变量到栈顶
3. 加载常量2
到栈顶
4,比较
如果大于后者等于跳转到13 然后1号局部变量 自增1 然后下一步顺序到16 return
否则就是顺序执行到7 1号局部变量
增量为-1 自增运算 然后到10 ,10为跳转到16 return
方法调用相关指令
public voidinvoker() {
method(2);
}public void method(inti) {if(i>5) {
System.out.println(i);
}
}
invoker()
0,加载0号
局变量到栈 (上面基本都是第一个数据被保存到1号局部变量,0 号其实是被this 占用了)
1,加载常量2
到操作数栈
2.调用实例方法(I)V
5 return
method(int)
0. 加载1号局部变量到操作数栈
1.
加载常量5 到操作数栈
2比较如果小于等于 跳转到12行 直接返回
如果大于
那么顺序执行到5行 out 是类型为PrintStream的 System中的静态变量
8
加载1号局部变量到操作数栈
9 调用实例方法 println 是 PrintStream的实例方法
使用invokevirtual
switch 相关
int i = 5;int j = 6;switch(i) {case 1:
j= j + 1;break;case 3:
j= j + 2;break;case 5:
j= j + 3;break;default:
j= j + 4;
}
0,1,2,4
分别将 5 和 6 加载并存储到1号和2号局部变量
5.加载1号局部变量到栈 对应 switch (i)
{
然后根据tableswitch 表 进行跳转
虽然我们只有1,3,5 但是设置了1到5 ,对于2 和 4
直接跳转到default
40: 2号局部变量 +1
顺序到43
43: 跳转到61 return
46: 2号局部变量 +2
顺序到49
49: 跳转到61 return
52: 2号局部变量 +3
顺序到55
55: 跳转到61 return
58 2号局部变量 +4
顺序到61
return
int j = 6;
String string= "hehe";switch(string) {case "A":
j= j + 1;break;case "hehe":
j= j + 2;break;case "C":
j= j + 3;break;default:
j= j + 4;
}
0 加载常量6到栈
1 保存到 1 号局部变量
3.加载常量池 #36 到栈
5 保存到2 号局部变量
6 加载2号局部变量
到栈
7 复制栈顶元素
8 复制的元素保存到3号局部变量
9 调用String实例化方法hashCode
12,
lookupswitch表中,不在类似tableswitch 了,那个是连续的
lookupswitch 是不连续的
我们总共有三个case一个default
lookupswitch 总共有4项
"A" 的hashCode 为 65
"C" 的hashCode为 67
"hehe" 的hashCode为 3198650 不信的话,自己写个main方法打印下
经过12行
路由之后跳转到指定的序列
你会发现三个case他们的过程是一样的
加载3号局部变量 ,然后将常量 A C hehe
也加载到栈
然后调用equal方法进行比较
代码千千万,本文只是找一些基本的示例展示字节码与代码的对应关系,想要熟悉这块
唯有没事多javap看看你代码的class文件,才能通宵领悟,进而更好地优化你的代码
比如看看下面的一个很典型的例子
int i = 5;int j = 8;int k = i+j;int l = 3+6;
前一部分:
0.
常量5 加载到栈
1,保存到 1号局部变量
2. 常量8 加载到栈
4 保存到2号 局部变量
5,加载1号局部变量
6,
加载2号局部变量
7 执行iadd 结果会压入栈顶
8 栈顶元素保存到3号局部变量
至此
完成了前三行代码
后一部分:
9.常量9 加载到栈 (3+6 已经被计算好了)
11,保存到4号局部变量
java虚拟机编译_[四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式...相关推荐
- Java的Class类文件结构及基本字节码指令
Class类文件的结构 概念:Class文件是一组以8位字节为基础单位的二进制流 按顺序整齐排列 没有任何分隔符,内容全部是运行时的必要数据,没有空隙存在 排序方式:高位在前 Big-Endian:最 ...
- JVM学习-字节码指令
目录 1.入门 2 javap 工具 3 图解方法执行流程 3.1.原始 java 代码 3.2.编译后的字节码文件 3.3.常量池载入运行时常量池 3.4.方法字节码载入方法区 3.5.main 线 ...
- JAVA数组子集_【Java虚拟机】JVM系列学习之JVM体系(一)
一.前言 为什么要学习了解Java虚拟机 1.我们需要更加清楚的了解Java底层是如何运作的,有利于我们更深刻的学习好Java. 2.对我们调试错误提供很宝贵的经验. 3.这是合格的Java程序必须要 ...
- JVM笔记:Java虚拟机的字节码指令详解
1.字节码 Java能发展到现在,其"一次编译,多处运行"的功能功不可没,这里最主要的功劳就是JVM和字节码了,在不同平台和操作系统上根据JVM规范的定制JVM可以运行相同字节码( ...
- 【深入理解java虚拟机】 - JVM字节码指令介绍
文章目录 什么是字节码指令 javap的用法 字节码与数据类型 字节码指令集 加载和存储指令 运算指令 类型转换指令 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指 ...
- java动态语言_探秘Java 7:JVM动态语言支持详解
JDK 7 增加了对 JSR 292 的支持,在 JVM 中动态类型语言的运行速度将变得更快.这一支持的关键在于增加了新的 Java 字节码,invokedynamic,它用于方法调用,还有新的连接机 ...
- Java虚拟机专题之字节码指令(读书笔记)
一 字节码与数据类型 大部分的指令都包含了其操作所对应的数据类型信息. 比如iload指令用于从局部变量表中加载int类型的数据到操作栈中,而fload指令加载的则是float数据类型的数据. 二 加 ...
- Java虚拟机字节码指令概述
虚拟机字节码指令 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成. 一 基 ...
- idea如何反编译字节码指令_美团点评:Java字节码增强技术,线上问题诊断利器...
作者简介:泽恩,美团到店住宿业务研发团队工程师.文章转载于公众号:美团技术团队 1. 字节码 1.1 什么是字节码? Java之所以可以"一次编译,到处运行",一是因为JVM针对各 ...
最新文章
- 37张图详解MAC地址、以太网、二层转发、VLAN
- 互联网与大数据将成为智能制造的基石
- Thymeleaf循环遍历
- 关于Keychain
- mysql 语句块语法_MySQL ------ MySQL常用语句的语法 (三十四)
- 不用Oracle?基于MySQL数据库下亿级数据的分库分表
- mysql st_contains实现_MySQL实现树状所有子节点查询的方法
- DG SG childSG fatherSG
- 缓存能提高多少 php,提高PHP编程效率 引入缓存机制提升性能
- 二等水准数据平差_二等水准平差软件
- Keil v5安装和使用
- Funcode实现打飞虫1
- 软件测试面试题(一)
- python窗口截图快捷键_python 自定义截图快捷键
- java实现 腾讯人机验证 + 前端
- word如何设置每一章节的页眉都不同
- latex 公式编号的自定义
- UnicodeDecodeError: 'gbk' codec can't decode byte 0x91 in position 8: illegal multibyte sequence
- PCL滤波工具之StatisticalOutlierRemoval深度分析
- 企业拥有PMO(项目管理办公室)的好处
热门文章
- Java基本语法(13)--条件分支switch-case结构
- linux删除含有特殊字符的行,Linux 删除带有特殊字符的文件
- php node 目录,node.js基于fs模块对系统文件及目录进行读写操作的方法详解
- centos8.2安装mysql_为CentOS 8操作系统安装MySQL的方法,以安装MySQL 8为例
- 数字通信系统中的均衡技术
- P1488 肥猫的游戏
- AliOS-Things Visual studio code helloworld 入门
- 再论EM算法的收敛性和K-Means的收敛性
- HDU 3665 Seaside
- Zabbix-3.0.0 安装Graphtree