字节码文件详解
学习JVM
目前先跟着B站中的黑马程序员的课程黑马程序员JVM虚拟机入门到实战全套视频教程
Java虚拟机的组成
JVM是由类加载器(ClassLoader)、运行时数据区域(JVM管理的内存)和执行引擎(即时编译器、解释器、垃圾回收器等
)组成的
- 类加载器
类加载器负责加载class字节码文件中的内容到内存中 - 运行时数据区域
负责管理JVM使用到的内存,比如创建对象和销毁对象 - 执行引擎
将字节码文件中的指令解释称机器码,同时使用即时编译器优化性能。
字节码的应用场景
- 解决面试题:
int i = 0; i = i++; //最终i的值是多少?
// 如下的执行结果是多少?
int i = 1;
i += i += ++i + 4.4 + i;
System.out.println(i);
Java的反射是如何实现的?
-
解决工作中的实际问题 - 版本冲突
-
解决工作中的实际问题 -系统升级
有时候系统明明升级了,为什么Bug还是存在呢?
这些问题都是跟字节码有关的,想要正确的学习字节码文件,以正确的姿势打开字节码文件是必不可少的。
以正确的方式打开文件
- 字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。通过NotePad++使用十六进制插件查看class文件:
可以明显的看到,是一堆十六进制字符,根本没法直观地进行阅读。
如果有一款软件可以打开字节码文件,以一种直观地方式展现就好了。
接下来就推荐一款软件
jclasslib工具
- 通过观看B站视频,其中推荐使用的jclasslib工具查看字节码文件。
- GitHub地址
通过jclasslib工具打开class文件后可以清晰的看到文件中的数据信息。
已经用正确的姿势打开了class文件了就可以去查看了解class字节码文件中是如何分配保存我们的java文件信息的了~
字节码文件的组成
先看一看字节码文件的组成
图中带有星号
的信息是重点信息,属于字节码文件中的重要部分,其他信息了解一下有概念就ok。
基本信息
通过jclasslib打开class文件后可以看到。
其中一般信息和接口可以统称为基础信息。
基础信息中含有:魔数、字节码文件对应的Java版本号、访问标识(public final等等)、父类和接口
Magic魔数
魔数是class文件的开头的四个字符,软件根据文件的开头的魔数来判断是否是可解析的文件类型。
可以使用jclasslib工具打开class文件。可以看到文件开头上有cafebabe的字符。
- 文件是
无法通过文件扩展名
来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该类型就会出错。
- Java字节码文件中,将文件头成为
magic魔数
。
具体小伙伴可以使用NodePad++的十六进制打开文件看看具体的文件头。也可以去测试一下,比如将视频文件的后缀名修改后,使用播放器是否可以正常打开。
主副版本号
主副版本号指的是编译字节码文件的JDK版本号
,主版本号用于表示大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容。
1.2之后的大版本号计算方法就是:主版本号-44
。比如:主版本号是52的就是JDK8
案例
需求: 解决以下由主版本号不兼容导致的错误
两种方案:
- 升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试,所以
慎用
) - 将第三方依赖的版本号降低或更换其他依赖,以满足JDK版本的要求(
建议采用
)
汇总
常量池
class文件中的常量池中保存了字符串常亮、类或接口名、字段名。主要在字节码指令中使用
。
- 字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。
如果我们没定义一个string变量,不管内容是否相同都在字节码文件中保存一份,这必然是对存储空间的一种浪费。
合理的做法肯定是将上例中的"我爱北京天安门"这个字符串只存储一份,创建的两个str1和str2变量的地址指向同一个即可。
于是就有了常量池中的信息
- 常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。
- 字节码指令中通过编号引用到常量池的过程称之为
符号引用
。
通过#7符号引用,就可以完成str1和str2变量都指向同一个字符串了,节省了空间。
字段
字段中保存了当前类的接口声明的字段信息
。
方法
方法中保存了当前类或接口声明的方法信息和字节码指令
。
其中字节码指令
是我认为较为重要的,可以通过这些字节码指令清晰的了解到方法的执行顺序以及其中变量值的变化过程。
先看个开头的面试题:
int i = 0; i = i++; //最终i的值是多少?
刚开始学习java的时候,我可能会说:应该是1吧,i++的优先级高,先执行之后将结果1复制给i,所以i应该是1。
学习过Java和JVM后,再回想一下以前的关于优先级计算的问题,就感觉简单的很了。
正确
的答案不是1,最终的i的值还是0。
那为什么还是0呢。让我们来学习看一下字节码中的方法中的字节码指令具体都干了什么。
-
字节码中的方法区是存放
字节码指令
的核心位置,字节码指令的内容存放在方法的Code属性中。
(可以通过字节码指令右键显示JVM规范,会跳转到官网,可以查看每一个指令的作用) -
操作数栈是临时存放数据的地方,局部变量表是存放方法中的局部变量的位置。
(其中什么是操作数栈和局部变量表我们后面再详细了解,目前先了解主要思路后续一点点的深入了解都是什么。)
先看个例子来了解一下字节码指令。
- iconst_0:往操作数栈中放入0;
- istore_1:从操作数栈取出存放的数值放入局部变量表1号位置;
- iload_1:将局部变量表1中的数据放入操作数栈中
- iconst_1:将常亮1放入操作数栈中。现在操作数栈中应该有两个数据分别是0和1。
- iadd:将操作数栈顶部的两个数据进行累加,放入栈中。现在操作数栈中应该是1
- istore_2:将操作数栈中的数据放入局部变量表2中;
- 最终局部变量表1是0,2是1。
已经看完了上述例子,再来看看刚才的那个面试题。
根据字节码指令:
- 现在操作数栈中放入常亮0
- 将操作数栈中的数据放入局部变量表中的1
- 将局部变量表1的数放入操作数栈中。目前操作数栈中有1
- iinc 1 by 1:将局部变量表1的数加1。所以现在情况是操作数栈有1,局部变量表的1是2
- 将操作数栈的数让如局部变量表1中。即局部变量表1最终数据应为1.
看完了i = i++;的字节码指令,再来看看i = ++i;的字节码指令他们之间有什么不同。
根据字节码指令:
- 现在操作数栈中放入常亮0
- 将操作数栈中的数据放入局部变量表中的1
- 将局部变量表1的数加1。所以现在情况是操作数栈为空,局部变量表的1是2
- 将局部变量表1的数放入操作数栈中。目前操作数栈中有2
- 将操作数栈的数让如局部变量表1中。即局部变量表1最终数据应为2。
通过上述几个字节码指令的例子,相信大家已经知道该如何解答刚才面试官的问题了。
int i = 0; i = i++; //最终i的值是多少?
答案是0,我通过分析字节码指令发现,i++先把0取出来放入临时的操作数栈中,接下来对i进行了加1,i变成了1,最后再将之前保存在临时操作数栈的值0放入i,最终i就变成了0。
案例
问题:
通过字节码指令分析下面三种“加一”的操作性能的高低
小伙伴们可以通过jclasslib打开字节码指令看一看具体的指令,这里就不贴图了。
- 其中应该i++性能最高,因为相比于其他两个减少了将变量数据放入操作数栈中、往操作数栈放入常量、操作数栈相加操作以及将操作数栈数据放入局部变量表的步骤。
- j = j + 1;和 k += 1;的字节码指令应该是相同的,都应该是x = x + 1;的样式,x代表j和k。
属性
属性中保存了类的属性,比如源码的文件名、内部类的列表等
玩转字节码常用工具
javap -v命令
- javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。
适合在服务器上查看字节码文件内容。
- 直接输入javap查看所有参数。
- 输入
javap -v 字节码文件名称
查看具体的字节码信息。(如果jar包需要先试用jar -xvf命令解压)
jclasslib插件
- jclasslib也有IDEA插件版本,建议开发时使用IDEA插件版本,可以在代码
编译之后
实时看到字节码文件内容。
阿里arthas
- Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
- 官网:https://arthas.aliyun.com/doc/
- dump类的全限定名:dump已加载类的字节码文件到特定目录。
- jad类的全限定名:反编译已加载类的源码。
案例
背景:
小李的团队昨天对系统进行了升级修复了某个bug,但是升级完之后发现bug还是存在,小李怀疑是因为没有把最新的字节码文件部署到服务器上,请使用阿里的arthas去确认升级完的字节码文件是不是最新的。
思路:
- 在出问题的服务器上部署一个arthas并启动;
- 连接arthas的控制台,使用jad命令加上想要查看的类名,反编译出源码;
- 确认源码是否是最新的。
汇总
如何查看字节码文件?
-
本地文件可以使用jclasslib工具查看,开发环境使用jclasslib插件。
-
服务器上文件使用javap命令直接查看,也可以通过arthas的dump命令导出字节码文件再查看本地文件。还可以使用jad命令反编译出源代码。