【Java】JVM字节码分析
一、功能
1、工作原理
Java编写的程序必须要先结果编译,但是这个步骤并不会生成特定平台的机器码。而是会生成一种平台无关性的.class文件。再由Java虚拟机来解释这个.class文件生成与平台相关的机器码,执行这个生成的机器码。达到跨平台的目的。
2、解释和运行
jvm本质上是运行在计算机上的程序,负责运行java字节码文件。
对字节码文件中的指令,实时的解释成机器码,计算机执行生成的机器码。
所以说,Java是编译与解释型语言,也可以说是不完全编译型语言。因为必须要先经过编译,JVM才能解释成字节码。
3、内存管理
自动为对象、方法等分配内存。自动垃圾回收机制,回收不再使用的对象。垃圾回收器会定期检查不再使用的对象,并回收它们占用的内存空间。JVM通过垃圾回收器来管理堆内存中的对象分配和释放。
4、 即时编译
在Java中每次执行都需要实时解释字节码文件成机器码,导致效率较低、速度变慢。这么做的原因是因为需要跨平台,不同操作系统的Java虚拟机不同,解释的也不一样,不同的虚拟机会转成当前操作系统的机器码。在不同的平台上JVM是不一样的,解释的机器码自然也是不一样的。
即时编译为了解决这个性能问题。JVM会识别热点代码(短时间多次调用), 会主动优化并且解释成机器码 ,将这个机器码保存在内存中。下次如果调用这段热点代码会直接从内存中取出调用。这样就省略了一次解释的步骤。这样在某些情况下性能就会提升很大 。
二、解释字节码
使用工具jclasslib工具查看class字节码
GitHub地址:https://github.com/ingokegel/jclasslib
1、分析class文件
public class HelloWorld {
public static void main(String[] args) {
int i = 0 ;
int j = i+1;
System.out.println(j);
}
}
对应的.class字节文件为
0 iconst_0 | 将常量0放入操作数栈中 |
---|---|
1 istore_1 | 将操作数栈顶的数值存储到局部变量表1的位置 |
2 iload_1 | 将局部变量表1中的数复制到栈上 |
3 iconst_1 | 将常量1放入操作数栈中栈中 |
4 iadd | 将栈中最上面两个值进行相加,存储到栈顶 |
5 istore_2 | 从栈中取出操作数放入局部变量表中2号位置 |
13 return | 方法结束 |
2、分析i++
public class HelloWorld {
public static void main(String[] args) {
int i = 0 ;
i = i++;
System.out.println(i);
}
}
0 iconst_0 | 将常量0放入操作数栈中 |
---|---|
1 istore_1 | 将操作数栈顶的数值存储到局部变量表1的位置 |
2 iload_1 | 将局部变量表1中的数复制到栈上 |
3 iich 1 by 1 | 将布局变量表1的位置的值加1 |
6 istore_1 | 将栈顶元素取出存储到局部变量表1的位置 |
10 iload_1 | 将布局变量表1位置的操作数复制到操作数栈 |
4 return | 从栈中取出操作数放入局部变量表中2号位置 |
13 return | 方法结束 |
这里由于2 iload_1
指令比 3 iich 1 by 1
先执行,导致复制到操作数栈上的操作数没能加上,并且最后将其取出,导致加1的值被覆盖,所以最后的值为0,结果输出为0
3、分析++i
public class HelloWorld {
public static void main(String[] args) {
int i = 0 ;
i = ++i;
System.out.println(i);
}
}
0 iconst_0 | 将常量0放入操作数栈中 |
---|---|
1 istore_1 | 将操作数栈顶的数值存储到局部变量表1的位置 |
2 iinc 1 by 1 | 将布局变量表1的位置的值加1 |
5 iload_1 | 将布局变量表1位置的操作数复制到操作数栈 |
6 istore_1 | 将栈顶元素取出存储到局部变量表1的位置 |
10 iload_1 | 将布局变量表1位置的操作数复制到操作数栈 |
14 return | 方法结束 |
相比于之前来说, iinc 1 by 1
在iload_1
之前,先加1再复制到操作数栈中。所以取出来覆盖掉也是加1后的值。输出结果为1
三、总结
由于最后运行的是生成的机器码,而机器码是由JVM解释而来,那么排除代码层面的优化,我们可以查看编译成的.class文件中的字节指令,可以详细查找出代码中可能出现的问题,以及代码运行流程,快速的找出性能瓶颈。