JVM入门(1)初识JVM与字节码文件详解

JVM学习之路(1)初识JVM与字节码文件

目录

初识JVM

字节码文件详解

Java虚拟机的组成

字节码文件的组成

基本信息

魔数

主副版本号

其他基础信息

常量池

方法

常用字节码工具

常用工具 javap - v

常见工具 jclasslib插件

常见工具 arths


欢迎关注我的博客!26届java选手,一起加油💘💦👨‍🎓😄😂


初识JVM

JVM是java虚拟机,本质上是一个运行在计算机上的程序,他的职责是运行java字节码文件。

JVM的功能有:

  • 解释和运行:对字节码文件中的指令,实时的解释成机器码,让计算机执行。
  • 内存管理:自动为对象,方法等分配内存;自动的垃圾回收机制,回收不再使用的对象。
  • 即时编译:对热点代码进行优化,提升执行效率。

同时,为了跨平台性,牺牲了部分性能进行实时编译:

即时编译是怎么做的?对于热点字节码文件,进行解释后放到内存,下次再调用无需解释直接调用。

使用cmd和java -version可以查,我们的HotSpot虚拟机

常见的JVM虚拟机还有HotSpot, GraaLVM,OPENj9等,另外DragonWell龙井JDK也提供了一款功能增强版的JVM。其中使用最广泛的是HotSpot虚拟机。

字节码文件详解

Java虚拟机的组成

主要是类加载器,运行时数据区域,垃圾回收器,即时编译器(后续讲解),而解释器和本地接口程序员无法修改和接触到源码,做了解即可。

字节码文件的组成

基本信息

魔数
  • 文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
  • 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
  • Java字节码文件中,将文件头称为magic魔数。

主副版本号
  • 主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1; 副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。
  • 版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容

        1.2之后大版本号计算方法就是主版本号-44比如主版本号52就是JDK8

主版本号不兼容导致的错误

有两种解决方法:
1.升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试)

2.将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求(建议采用)

其他基础信息

访问标识和类、父类、接口索引。

常量池

字节码中常量池的作用:避免相同的内容重复定义,节省空间

如下的两个字符串,实际上就是相同内容的重复定义。

线程池节省空间的原理是有多个相同常量的时候,只需要在常量池中存放一份字符串,再被引用就可以节省空间

以如下代码进行示范:

public class ConstantPoolTest {
    public static final String a1 = "我喜欢吃蛋糕";
    public static final String a2 = "我喜欢吃蛋糕";
    public static void main(String[] args) {
        ConstantPoolTest constantPoolTest = new ConstantPoolTest();
    }
}

编译后打开jcclasslib,可以看到如下信息:

两个String常量都指向一个索引,而我们进入这个索引后,可以看到又指向了下一个索引:

继续进入,终于看到了最后的字符串字面量。

为什么要先找字符串,再找字面量?而不是直接找到字面量呢? 不能直接通过字段找到字面量吗?答案是不可以的,因为java中有一个字符串常量池,当字节码文件被解析并加载之后,需要把String类型的常量池中的内容加载到字符串的常量池中,所以必须要有一个类型为String。

  • 常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。
  • 字节码指令中通过编号引用到常量池的过程称之为符号引用

方法

一个有意思的面试题如下:

这个面试题的答案简单,但是我们从来没深入的理解原理。

字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中

操作数栈是临时存放数据的地方,局部变量表是存放方法中的局部变量的位置:

举例:

public static void main(String[] args) {
    int i = 0;
    int j = i + 1;
}

可以看到我们定义的i和j都在‘名字’这一列中。

处理过程如下:

首先	 
int i = 0; 
iconst_0 :把0放到操作数栈中
istore_1 把0赋值给1号位置,此处的1号位置就存放了i
int j = i + 1;
iload_1 :把1号位置复制到操作数栈,而不是像store那样移走,只是复制
iconst_1: 把数字1放到操作数栈中
iadd :两数相加
istore_2 : 把结果放到位置为2的地方,此处是j。
return 结束

了解完以上举例的字节码执行流程,接下来看一开始提到的题目:

        i = i ++ 的执行流程:

先把0放到操作数栈,然后把0放到局部变量表数组的1号位置,

i_load复制到操作数栈中,此时局部变量中的1号位置还有i = 0 ;

然后到 iinc 1 by 1 是在局部变量表1号位置增加1,而不用取到操作数栈 ,此时操作数栈还是0 ,而局部变量表中的 i 已经 为 1 了

 

执行到 istore_1 的时候就把操作数栈中的 0 又放回到局部变量表中了, 所以 i 依然是 0;

i++和++i是load和increase 的顺序有区别导致结果不同了。先iinc 1 by 1 导致了在局部变量表已经变成1 了,取到操作数栈再存回来也依然是1 。

常用字节码工具

常用工具 javap - v

javap 是 JDK 自带的反编译工具, 可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件内容

直接输入javap查看所有参数。

输入javap -v 字节码文件名称 查看具体的字节码信息。(如果jar包需要先使用 jar –xvf 命令解压

常见工具 jclasslib插件

jclasslib也有Idea插件版本,建议开发时使用Idea插件版本,可以在代码编译之后实时看到字节码文件内容。

常见工具 arths

  • Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
  • 官网: https://arthas.aliyun.com/doc/

通过 java -jar arthas-boot.jar启动,

输入想进入的进程前方的数字就可以进入内部查看细节。

arthas 有许多的功能:

dump 类的全限定名:dump已加载类的字节码文件到特定目录。

jad 类的全限定名: 反编译已加载类的源码。

思路:

  1. 在出问题的服务器上部署一个 arthas,并启动。
  2. 连接arthas的控制台,使用jad命令加上想要查看的类名,反编译出源码
  3. 确认源码是否是最新的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值