03.字节码文件详解
JVM的组成
从字节码文件的执行流程看JVM的组成:
1.准备好字节码文件(自己编译或其他人传给你)
2.使用类加载器ClassLoader加载(此时JVM已参与)
3.运行时数据(JVM管理的内存)
4.执行引擎(即时编译器、解释器垃圾回收器等):将字节码文件中的指令解释成机器码,同时使用即时编译器优化性能
字节码文件组成
以正确的姿势打开文件
字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。
例如通过NotePad++使用十六进制插件查看class文件:
不直观。
因此,推荐使用jclasslib工具查看字节码文件。
Github地址:https://github.com/ingokegel/jclasslib
Class字节码文件的内容
- 基础信息魔数、字节码文件对应的Java版本号访问标识(public final等等)父类和接口
- 常量池保存了字符串常量、类或接口名、字段名主要在字节码指令中使用
- 字段当前类或接口声明的字段信息
- 方法当前类或接口声明的方法信息字节码指令
- 属性类的属性,比如源码的文件名内部类的列表等
字节码文件的基本信息
字节码文件的组成部分-Magic魔数
●文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
●软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
字节码文件的组成部分-主副版本号
主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。
1.2之后大版本号计算方法就是:
主版本号-44
比如主版本号52就是JDK8
主版本号不兼容导致的错误
需求:
解决以下由于主版本号不兼容导致的错误
类文件具有错误的版本52.0,应为50.0请删除该文件或确保该文件位于正确的类路径子目录中。
解决方法:
两种方案:
1.升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试)
2.将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求
一般选择第二种方法解决。
升级JDK版本:
降低依赖的版本:
小结
字节码文件的常量池和方法
常量池
字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。字节码指令中通过编号引用到常量池的过程称之为符号引用。
方法
字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。
源代码:
public class Demo1 {
public static void main(String[] args) {
int i = 0;
i = i++;
System.out.println(i);
}
}
标准答案:答案是0,我通过分析字节码指令发现,i++先把0取出来放入临时的操作数栈中,接下来对i进行加1,i变成了1,最后再将之前保存的临时值0放入i,最后i就变成了0。
如果改成i = ++i;则最后结果为1,他们的字节码指令顺序有了掉换。
常用指令学习
示例代码:
public class Demo2 {
public static void main(String[] args) {
int i = 0;
int j = i + 1;
}
}
对应的字节码指令:
在程序执行时有操作数栈(临时存放)和局部变量表数组(局部变量存放位置),如下图:
- iconst_x指令,将常量x存入操作数栈
- istore_x指令,将操作数栈最上面的数存入编号为x的局部变量表数组中
- iload_1指令,将序号为x的局部变量存入操作数栈
- iinc指令,将局部变量增加常数。该指令的格式一般为:iinc x by i,意为将本地变量表里编号为x的数加上i,并将结果存到原地。这个指令比较特别,它直接将局部变量表中的数值加上指定的值,结果直接保存在局部变量在局部变量表中原来的位置,不通过操作数栈。
练习
查看字节码文件并解答问题。问题:
通过字节码指令分析下面三种“加一”的操作性能的高低?
一般来说,字节码指令越多则相应的性能就会越低。
Java代码:
public class Test1 {
public static void main(String[] args) {
int i = 0, j = 0, k = 0;
i++;
j = j + 1;
k += 1;
System.out.printf("i=%d j=%d k=%d",i,j,k);
}
}
编译后class文件的本地变量表(Loacl Variable Table):
指令列表:
(指令列表不包含最后的输入语句部分)
i++操作:第7行
j = j + 1操作:第8行 到 第11行
k += 1操作:第12行
因此,i++操作与k += 1的效率相当,优于j = j + 1操作。