• 【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文件中的字节指令,可以详细查找出代码中可能出现的问题,以及代码运行流程,快速的找出性能瓶颈。

  • 相关阅读:
    JavaScript 的真值与假值 | 如何判断一个对象为空
    MySQL 表分区使用实践
    SAP Success Factor Single Sign On(单点集成) 的文档清单
    网络安全红队详细接收
    Vue中的路由介绍以及Node.js的使用
    【Python教学】pyqt6入门到入土系列,超详细教学讲解
    Vue挂载(mount)和继承(extend)
    中华保险面试题记录
    一种基于网络流量风险数据聚类的APT攻击溯源方法
    java对象深拷贝(Mapstruct)代码实现
  • 原文地址:https://www.cnblogs.com/changwan/p/18233599