0
点赞
收藏
分享

微信扫一扫

Java字节码文件详解​

03.字节码文件详解

JVM的组成

从字节码文件的执行流程看JVM的组成:

1.准备好字节码文件(自己编译或其他人传给你)

2.使用类加载器ClassLoader加载(此时JVM已参与)

3.运行时数据(JVM管理的内存)

4.执行引擎(即时编译器、解释器垃圾回收器等):将字节码文件中的指令解释成机器码,同时使用即时编译器优化性能

Java字节码文件详解​_局部变量



字节码文件组成

以正确的姿势打开文件

字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。

例如通过NotePad++使用十六进制插件查看class文件:

Java字节码文件详解​_版本号_02


不直观。

因此,推荐使用jclasslib工具查看字节码文件。

Github地址:https://github.com/ingokegel/jclasslib

Java字节码文件详解​_局部变量_03



Class字节码文件的内容

Java字节码文件详解​_版本号_04


  • 基础信息魔数、字节码文件对应的Java版本号访问标识(public final等等)父类和接口
  • 常量池保存了字符串常量、类或接口名、字段名主要在字节码指令中使用
  • 字段当前类或接口声明的字段信息
  • 方法当前类或接口声明的方法信息字节码指令
  • 属性类的属性,比如源码的文件名内部类的列表等


字节码文件的基本信息

字节码文件的组成部分-Magic魔数

●文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。

●软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。

Java字节码文件详解​_版本号_05



字节码文件的组成部分-主副版本号

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

Java字节码文件详解​_局部变量_06


1.2之后大版本号计算方法就是:

主版本号-44

比如主版本号52就是JDK8


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

需求:

解决以下由于主版本号不兼容导致的错误

类文件具有错误的版本52.0,应为50.0请删除该文件或确保该文件位于正确的类路径子目录中。

解决方法:

两种方案:

1.升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试)

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

一般选择第二种方法解决。

升级JDK版本:

Java字节码文件详解​_局部变量_07



Java字节码文件详解​_局部变量_08



Java字节码文件详解​_版本号_09



降低依赖的版本:

Java字节码文件详解​_局部变量_10



小结

Java字节码文件详解​_字节码_11



字节码文件的常量池和方法

常量池

字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。字节码指令中通过编号引用到常量池的过程称之为符号引用

Java字节码文件详解​_版本号_12



方法

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

源代码:

public class Demo1 {
 public static void main(String[] args) {
 int i = 0;
 i = i++;
 System.out.println(i);
 }
}


Java字节码文件详解​_局部变量_13



标准答案:答案是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;
 }
}

对应的字节码指令:

Java字节码文件详解​_字节码_14


在程序执行时有操作数栈(临时存放)和局部变量表数组(局部变量存放位置),如下图:

Java字节码文件详解​_版本号_15


  • iconst_x指令,将常量x存入操作数栈
  • istore_x指令,将操作数栈最上面的数存入编号为x的局部变量表数组中
  • iload_1指令,将序号为x的局部变量存入操作数栈
  • iinc指令,将局部变量增加常数。该指令的格式一般为:iinc x by i,意为将本地变量表里编号为x的数加上i,并将结果存到原地。这个指令比较特别,它直接将局部变量表中的数值加上指定的值,结果直接保存在局部变量在局部变量表中原来的位置,不通过操作数栈。


练习

查看字节码文件并解答问题问题:

通过字节码指令分析下面三种“加一”的操作性能的高低?

Java字节码文件详解​_版本号_16


一般来说,字节码指令越多则相应的性能就会越低。

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):

Java字节码文件详解​_字节码_17


指令列表:

Java字节码文件详解​_局部变量_18


指令列表不包含最后的输入语句部分

i++操作:第7行

j = j + 1操作:第8行 到 第11行

k += 1操作:第12行

因此,i++操作与k += 1的效率相当,优于j = j + 1操作。






举报

相关推荐

0 条评论