字节码文件详解
为什么学字节码文件
- 解决面试难题
- 解决实际项目的版本冲突问题:类文件具有错误的版本52.0,应为50.8,请删除该文件或确保该文件位于正确的类路径子目录中。
- 系统升级:明明系统升级了,为什么Bug还是存在呢?
如何以正确的姿势打开字节码文件?
字节码文件保存了源代码编译后的内容,是以二进制进行存储的,win上的记事本、notepad++等软件都无法打开查看(乱码)。
软件
可以使用jclasslib
这个软件来查看字节码文件。
下载链接:https://github.com/ingokegel/jclasslib/releases/tag/6.0.5
IDEA插件(推荐)
当然,也可以使用IDEA插件,更加方便
不一定要打开class文件,点击java文件也可以
如果修改了java代码文件,字节码文件不会直接跟着改变,需要重新编译一下文件(或者运行一下当前的程序,也会执行编译)
字节码文件的组成
一个字节码文件由以下部分组成:
- 基础信息
- 常量池
- 字段
- 方法
- 属性
基本信息
主要包含如下信息:魔数、字节码文件对应的ava版本号访问标识(public final等等)、这个类的父类是哪个、实现了哪些接口
魔数
- **一个文件无法通过文件扩展名确定文件类型。**文件扩展名可以随意修改,不会影响文件的内容。
- 软件是通过文件的头几个字节去校验文件的类型,如果软件不支持该种类型就会出错。Java字节码文件中,将文件头(CAFEBABE)称为magic魔数。只有文件的文件头满足特定的结构,才会被认识是相应的文件
【png】
【字节码文件】
主副版本号
编译字节码文件的jdk版本号。
- 主版本号用来标识大版本号,副版本号用来标识小版本号,一般只关心主版本号。
- 主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,jdk1.2之后大版本号计算方法是:jdk几=主版本号-44。比如主版本号52就是jdk8,主版本号61就是jdk17,每升级一个大版本,版本号就加一。
- 副版本号是当主版本号相同时用来区分不同版本的标识。
- **版本号的作用是用来判断jdk版本是否与当前字节码文件兼容(例如jdk7不能兼容17的字节码),如果不兼容就不能用当前jdk解释该字节码文件。**版本号不兼容,升级jdk或者将第三方依赖的版本号降低或者更换依赖。一般来说都是采用第二种,因为影响比较小
案例
解决以下由于主版本号不兼容导致的错误
原因:用了低版本的JDK运行高版本的字节码文件
两种方案:
- 方案一:升级JDK版本(可能引发其他工具的版本问题,需要进行大量测试)
- 方案二(推荐):将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求,注意,高版本JDK是可以运行低版本字节码文件的
常量池
保存了字符串常量、类或接口名、字段名,主要在字节码指令中使用。
- 常量池主要保存的就是常量
- 每一个常量都有一个编号(编号从1开始)
- 字节码文件中所有的字符串都能在这里找到
- 常量池里边其实也是有分类型的:字符串对象(保存着指向字符串常量的编号)、方法名、类名、字符串常量
在字段和方法的字节码指令中,通过常量编号可以快速找到对应的常量。这种方式称为符号引用。
作用:避免相同内容重复定义,节省空间
常量再指向字面量
为什么字段先指向字符串对象,字符串对象再指向字符串的字面量
可以让字段名、字段值共用字面量,节省更多空间,如果直接将字面量信息存储在字符串对象里面,让字段名也引用这个字符串对象是奇怪的,因为字段名并没有字段类型,看如下案例:abc字段名也是"abc",值也是"abc",即字段名和字段值都共用字面量"abc"
字段
当前类或接口声明的字段信息,其实就是类的属性。字段名会指向常量池中的字符串。
方法
方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的code属性之中。字节码中“init”方法其实就是类的默认无参构造方法。
字节码执行时的数据结构
- 操作数栈是临时存放数据的地方
- 局部变量表是存放方法中的局部变量的位置
那局部变量表的索引为0的元素存储什么?存main方法参数
【指令执行流程】
istore_index指令
:从操作数栈中弹出一个数据,将其存储到局部变量表中,index是局部变量表数组的索引
对应 i = 0
iload_index指令
:将局部变量表中的数据复制一份出来,放到操作数栈中,进行运算或者比较。执行i + 1的时候,先iload_1
将i的值取出来
再iconst_1
将1放入操作数栈中
执行iadd
,将栈的元素0和1相加,得到1 (操作数栈中只剩下1)
执行istore_2
,将i+1的值赋值给j
面试案例
面试中会遇到一道比较经典的题:
-
int i=0;i=i++
结果是多少? - 或者
int i=0;i=++i
结果是多少?
这时候就可以从字节码指令来解释了。
i++ 字节码指令执行流程
iinc index by num
:将指定index的局部变量表的变量增加num
最终将操作数栈的数据弹出,存入局部变量表中,所以i还是被替换成了0
++i 字节码指令执行流程
不同增量方式的性能分析
public class JJJ {
public static void main(String[] args) {
int i = 0;
i++;
int j = 0;
j = j + 1;
int k = 0;
k += 1;
}
}
三种方式,哪种性能最高?答:字节码指令越少,性能越高
- 找到编译之后的字节码文件
- 使用jclasslib打开字节码文件
- 查看字节码指令
第二种方式效率较低
属性
类的属性,例如源码的文件名、内部类的列表……
字节码常见工具
javap
- javap -v命令:jdk自带的反编译工具,适合在服务器上查看字节码文件内容。输入
javap -v 字节码文件名称
可以查看字节码文件内容。
展示javap的所有参数
如果代码文件格式是jar包,需要先用jar -xvf
进行解压。
后面的目录结构就和项目的目录结构是一致的了,查看字节码文件之前,先复制文件的全路径
查看字节码文件信息,如下命令会将字节码文件相关信息打印到控制台中
直接在控制台上面查看可能不太方便,可以将这些信息存储到指定的txt文件中
jclasslib
- jclasslib:这个软件其实有idea的插件版本。在插件中用jclasslib进行查找即可。记得修改完源代码之后需要重新编译才能看到字节码文件的改变。
Arthas
- Arthas来自阿里,官网上有详细的文档。下载arthas的jar包,然后
java -jar arthas-boot.jar
运行,选择想要监控的Java进程,然后看文档找命令即可。查看字节码、反编译等等它都行。 - Arthas是一款线上监控诊断产品,通过全局视角实时查看应用Ioad、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
Arthas使用介绍
功能
- 监控面板:将进程的整体运行情况展示出来
- 查看字节码信息:查看一个类的字节码信息以及反编译出来的源代码
- 方法监控:对特定方法进行监控,获取其运行时间
- 类的热部署:对一个类进行修改之后,在不停止线上应用的条件下,直接替换对应类的字节码文件
- 应用热点定义:定位到高频被调用的方法,显示其火焰图,展示整体性能
安装
官网:https://arthas.aliyun.com/doc/
下载地址:https://github.com/alibaba/arthas/releases
使用
启动
进入对应的进程
常用命令
命令可以直接看官网的教程
清屏
dashboard
dashboard -i [刷新时间间隔] -n [刷新次数]
查看dashboard,每隔两秒刷新一次,刷新三次之后停止
dump命令> 获取运行中系统的字节码信息
jad命令> 将运行中的类反编译为源代码
源代码 通过类的字节码信息反编译得到
案例
小李的团队昨天对系统进行了升级修复了某个bug,但是升级完之后发现bug还是存在,小李怀疑是因为没有把最新的字节码文件部署到服务器上,请使用阿里的arthas去确认升级完的字节码文件是不是最新的。
【思路】
- 在出问题的服务器上部署一个arthas并启动
- 连接arthas的控制台,使用jad命令加上想要查看的类名,反编译出源码
- 确认源码是否是最新的
文章说明
该文章是本人学习 黑马程序员 的学习笔记,文章中大部分内容来源于 黑马程序员 的视频黑马程序员JVM虚拟机入门到实战全套视频教程,java大厂面试必会的jvm一套搞定(丰富的实战案例及最热面试题),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对 黑马程序员 的优质课程表示感谢。