Java字节码详解(一) class文件结构

您所在的位置:网站首页 class字节码的文件结构 Java字节码详解(一) class文件结构

Java字节码详解(一) class文件结构

2024-07-10 18:01| 来源: 网络整理| 查看: 265

文章目录 java代码运行过程字节码文件的结构1.1 Class文件的结构属性1.2 用一个简单的示例代码分析1.3 分析由 `javap -v ` 命令显示的字节码文件字节码文件属性类属性常量池方法表集合 总结

知识背景:JVM(Java Virtual Machine)是不能直接运行java的源码文件的,必须要经过编译变成字节码文件才能运行。

java代码运行过程

计算机是不能直接运行java代码的,必须要先运行java虚拟机,再由java虚拟机运行编译后的java代码。这个编译后的java代码,就是本文要介绍的java字节码。

为什么jvm不能直接运行java代码呢,这是因为在cpu层面看来计算机中所有的操作都是一个个指令的运行汇集而成的,java是高级语言,只有人类才能理解其逻辑,计算机是无法识别的,所以java代码必须要先编译成字节码文件,jvm才能正确识别代码转换后的指令并将其运行。

字节码文件的结构

class文件本质上是一个以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件中。jvm根据其特定的规则解析该二进制数据,从而得到相关信息。

Class文件采用一种伪结构来存储数据,它有两种类型:无符号数和表。这里暂不详细的讲。

下图是接下来本文的简单的java例子编译后的文件,已经将class文件转为16进制。可以看到,我们熟悉的java代码经过编译转换为只有机器能识别的数据。 在这里插入图片描述

1.1 Class文件的结构属性

我们先从整体看下java字节码文件包含了哪些类型的数据: 在这里插入图片描述

1.2 用一个简单的示例代码分析

新建一个Test.java文件,然后编辑

public class Test{ public static void main(String[] args){ Integer a=1; Integer b=2; Integer c=a+b; System.out.println(c+""); } }

将该代码进行编译并保存在当前目录下: Alt 编译完成目录下会多一个Test.class编译文件。使用16进制打开,内容如下: 在这里插入图片描述

在图中,前4个字节cafe babe就是魔数,紧接着魔数的4个字节代表的是Class文件的版本号。

第5,6个字节表示的是次版本号(minor version),在上图中为0000,说明class文件的次版本号为 0 。

第7,8个字节代表主版本号(major version),在上图中为0034,因为是16进制,计算可以得到该class文件的主版本号为52.

以此类推,根据java字节码的规则,可以依次解析成该字节码文件的所有内容。

当然,jdk中包含了一个可以将字节码文件“可视化”操作的命令javap,运行该命令,可以将java字节码文件解析为符合人类逻辑的文件。

查看java字节码可以使用 javap 命令,当然也可以使用IDE的插件进行查看(比如在Idea中使用jclasslib插件进行查看)

在当前目录下运行

javap -v Test.class

会出现我们可以正常阅读的字节码

Classfile /F:/Test.class Last modified 2018-10-21; size 752 bytes MD5 checksum 4848a65fcbc8b0bc8ce60eb32172471b Compiled from "Test.java" public class Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #13.#22 // java/lang/Object."":()V *** 省略部分代码 #13 = Class #36 // java/lang/Object #14 = Utf8 #15 = Utf8 ()V *** 省略部分代码 #22 = NameAndType #14:#15 // "":()V *** 省略部分代码 #36 = Utf8 java/lang/Object *** 省略部分代码 { public Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=1 0: iconst_1 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: astore_1 5: iconst_2 6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 9: astore_2 10: aload_1 11: invokevirtual #3 // Method java/lang/Integer.intValue:()I 14: aload_2 15: invokevirtual #3 // Method java/lang/Integer.intValue:()I 18: iadd 19: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: astore_3 23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 26: new #5 // class java/lang/StringBuilder 29: dup 30: invokespecial #6 // Method java/lang/StringBuilder."":()V 33: aload_3 34: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lan /StringBuilder; 37: ldc #8 // String 39: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lan /StringBuilder; 42: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 45: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 48: return LineNumberTable: line 3: 0 line 4: 5 line 5: 10 line 6: 23 line 7: 48 } SourceFile: "Test.java" 1.3 分析由 javap -v 命令显示的字节码文件

我们从上到下看看例子的字节码文件各个字段代表的意思

字节码文件属性 Classfile /F:/Test.class Last modified 2018-10-21; size 752 bytes MD5 checksum 4848a65fcbc8b0bc8ce60eb32172471b Compiled from "Test.java"

这一部分一看就很明白

Last modified 最后修改时间size: 该字节码文件大小MD5 checksum 该文件的md5Compiled from "Test.java"表示该字节码文件是由“Test.java”编译而来 类属性 public class Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER

这一部分表示Test类的各个属性

minor version: 0 表示可以支持最小的jdk版本,java的jdk都是向下兼容的,所以该值一般都为0major version: 52 编译Test.java的jdk版本,我使用的是jdk1.8,所以52代表的是jdk8flags: ACC_PUBLIC, ACC_SUPER 表示该类的访问属性

flags属性类型及含义如下

标志名称标志值含义ACC_PUBLIC0x0001是否为Public类型ACC_FINAL0x0010是否被声明为final,只有类可以设置ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语义.ACC_INTERFACE0x0200标志这是一个接口ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假ACC_SYNTHETIC0x1000标志这个类并非由用户代码产生ACC_ANNOTATION0x2000标志这是一个注解ACC_ENUM0x4000标志这是一个枚举 常量池 Constant pool: #1 = Methodref #13.#22 // java/lang/Object."":()V *** 省略部分代码 #13 = Class #36 // java/lang/Object #14 = Utf8 #15 = Utf8 ()V *** 省略部分代码 #22 = NameAndType #14:#15 // "":()V *** 省略部分代码 #36 = Utf8 java/lang/Object *** 省略部分代码

常量池,可以理解为java资源的仓库,JVM运行方法时需要用到的数据都会在这里拿。下面从第一个常量开始分析各个字符的意义:

#1 = Methodref #13.#22 // java/lang/Object."":()V

#1 表示常量池值的序号,该数字表示为第一个#1 = Methodref 表示第一个值是一个方法的引用#13.#22 表示该方法的完整属性还需要 #13和#22的来复合表示

#13 = Class #36 // java/lang/Object #36 = Utf8 java/lang/Object

#13 说明属性为class,而#36指明了该class为object类将 #13和36结合一起,#13就得属性就是一个class类,且其具体的类为java.lang.Object仔细观察 #13 后的注释,你发现了什么?其实在将java字节码可视化的时候,javap就已经将各个常量的属性都给关联好了

#22 = NameAndType #14:#15 // "":()V

NameAndType 这一看知道是代表名字和类型按照上述的方式解析,会发现#22的名字为 ,类型为()V表示无参数并且返回值为void

根据上面的步骤分析可以得出:#1 引用的方法就是Test类的初始构造方法

方法表集合 public Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0

这个Test()方法就是我们Test类中默认的构造方法,在我们没有重写或者没有指定对应的构造方法时,java编译的时候会默认生成一个空的构造方法。 下面我们来看看这个方法里面的各个字段代表的意思: descriptor: ()V

·descriptor· 表示该方法的描述,这里表示的是该方法的参数为空,且方法值为void

Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return stack 最大操作数栈,JVM运行时会根据这个值来分配栈帧(Frame)中的操作栈深度,此处为1locals 局部变量所需的存储空间,单位为Slot, Slot是虚拟机为局部变量分配内存时所使用的最小单位,为4个字节大小。方法参数(包括实例方法中的隐藏参数this),显示异常处理器的参数(try catch中的catch块所定义的异常),方法体中定义的局部变量都需要使用局部变量表来存放。值得一提的是,locals的大小并不一定等于所有局部变量所占的Slot之和,因为局部变量中的Slot是可以重用的。args_size方法参数的个数,这里是1,因为每个实例方法都会有一个隐藏参数this0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return 表示运行方法时,各个指令的顺序,该顺序在下一章会进行详细的介绍。LineNumberTable 该属性的作用是描述帧栈中局部变量与源码中定义的变量之间的关系。可以使用 -g:none 或 -g:vars来取消或生成这项信息,如果没有生成这项信息,那么当别人引用这个方法时,将无法获取到参数名称,取而代之的是arg0, arg1这样的占位符。 start 表示该局部变量在哪一行开始可见,length表示可见行数,Slot代表所在帧栈位置,Name是变量名称,然后是类型签名。 总结

至此,把java字节码文件的各个属性都简单的介绍了一次。当然,还有很多其他的属性没有在示例代码中体现出来,这里就不进一步的介绍。本篇文章目的让大家对java字节码有个清晰的认识,在大脑中有个基础的概念,以后如果想深入了解,就知道从哪一方面入手,进行快速的学习。

参考文章: 深入理解JVM-字节码详解 轻松看懂字节码文件 详解java字节码class文件 《深入理解java虚拟机》



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3