【Java】JVM字节码分析

一、功能

1、工作原理

whiteboard_exported_image (1).png
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 方法结束

whiteboard_exported_image.png

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 1iload_1 之前,先加1再复制到操作数栈中。所以取出来覆盖掉也是加1后的值。输出结果为1

三、总结

由于最后运行的是生成的机器码,而机器码是由JVM解释而来,那么排除代码层面的优化,我们可以查看编译成的.class文件中的字节指令,可以详细查找出代码中可能出现的问题,以及代码运行流程,快速的找出性能瓶颈。

posted @ 2024-06-05 18:52  changwan  阅读(26)  评论(0编辑  收藏  举报