关于class:我所知道JVM虚拟机之Class文件结构二解读字节码

34次阅读

共计 13431 个字符,预计需要花费 34 分钟才能阅读完成。

解读字节码之旅


接下来咱们采纳一个示例代码,编译成字节码文件进行解读解读

public class Demo{
    private int num = 1;
    public int add(){
        num = num + 2;
        return num;
    }
}

咱们将以后 Demo 类编译成字节码文件,并且应用 notepad++ 和对应的插件关上翻译翻译,如下图

接下来咱们把这些信息复制粘贴到一个 text 文档中,并在空格上进行, 切割转为 csv 的格局

因为一些 csv 的个性,咱们在新建到一个 excel 文档中,并将 20 替换成 00

接下来开始解读下面这一坨字节码,咱们依据上面这副图,进行解读看看

魔数:Class 文件的标记、Magic Number(魔数)

================================

每个 Class 文件结尾的 4 个字节的无符号整数称为魔数(Magic Number)

它的惟一作用是确定这个文件是否为一个能被虚拟机承受的无效非法的 Class 文件。

即: 魔数是 Class 文件的标识符,并且魔数值固定为 GxCAFEBABE。不会扭转。

应用魔数而不是扩展名来进行辨认次要是基于平安方面的思考,因为文件扩展名能够随便地改变

如果一个 Class 文件不以 exCAFEBABE 结尾,虚拟机在进行文件校验的时候就会间接抛出以下谬误:

Error: A JNI error has occurred,please check your installation and try again
Exception in thread "main"java.lang.ClassFormatError: Incompatible magic value 1885430635 in classfile StringTest

依据后面的这些信息,那么咱们的字节码文件进行标出信息

Class 文件版本号

================================

紧接着魔数的 4 个字节存储的是 Class 文件的版本号,同样也是 4 个字节

第 5 个和第 6 个字节所代表的含意就是编译的副版本号 minor_version

而第 7 个和第 8 个字节就是编译的主版本号 major_version

它们独特形成了 class 文件的格局版本号,譬如某个 Class 文件的主版本号为 M,副版本号为 m,那么这个 Class 文件的格局版本号就确定为 M.m。

版本号和 ava 编译器的对应关系如下表:

  • Java 的版本号是从 45 开始的,JDK 1.1 之后的每个 DK 大版本公布主版本号向上加 1
  • 不同版本的 Java 编译器编译的 Class 文件对应的版本是不一样的。

目前,高版本的 Java 虚拟机能够执行由低版本编译器生成 Class 文件, 然而低版本的 Java 虚拟机不能执行由高版本编译器生成的 Class 文件。否则 JVMN 会抛出 java.lang.UnsupportedClassVersionError 异样。

在理论利用中,因为开发环境和生产环境的不同,可能会导致该问题的产生。

因而,须要咱们在开发时,特地留神开发编译的 JDK 版本和生产环境中的 JDK 版本是否统一。

虚拟机 JDK 版本为 1.k (k >= 2)时,对应的 class 文件格式版本号的范畴为45.0 - 44+k.0(含两端)

咱们也能够应用 Binary Viewer 插件关上 Demo 的字节码文件,能够查看到对应十六进制翻译过去数值

常量池:寄存所有常量

================================

常量池是 Class 文件中内容最为丰盛的区域之一

常量池对于 Class 文件中的字段和办法解析也有着至关重要的作用。随着 Java 虚拟机的一直倒退,常量池的内容也日渐丰盛。能够说,常量池是整个 Class 文件的基石。

官网文档中也有提到在版本号之后,紧跟着的是常量池的数量,以及若任个常量池表项

常量池中常量的数量是不固定的,所以在常量池的入口须要搁置一项 u2 类型的无符号数,代表常量池容量计数值(constant_pool_count)。

与 Java 中语言习惯不一样的是,这个容量计数是从 1 而不是 0 开始的。

由上表可见,Class 文件应用了一个前置的容量计数器(constant_pool_count)加若干个间断的数据项(constant_pool)的模式来形容常量池内容。咱们把这一系列间断常量池数据称为常量池汇合。

常量池表项中用于寄存编译期间生成的各种字面量和符号援用

这部分内容将在类加载后进入办法区的运行时常量池中在放,在 JDK7 当前吧字符串常量池放堆空间

常量池计数器(constant_poo1_count)

因为常量池的数量不固定,时长时短,所以须要搁置两个字节来示意常量池容量计数值

常量池容量计数值(u2 类型): 从 1 开始,示意常量池中有多少项常量。

提醒:即 constant_pool_count=1,那么示意常量池中有 0 个常量项

那么 Demo 的字节码信息,咱们能够看看它的常量池计数器是什么了

其值为 0x0016, 掐指一算,也就是 22。须要留神的是,这实际上只有 21 项常量。索引为范畴是 1 -21。

通常咱们写代码时都是从 O 开始的,然而这里的常量池却是从 1 开始,因为它把第 O 项常量空进去了。

这是为了满足前面某些指? 常量池的索引值的数据在特定状况下 须要表白“不援用任何一个常量池我的项目”的含意,这种状况可用索引值 0 来示意

constant_pool [](常量池)

constant_pool 是一种表构造,以 1 ~constant_pool_count – 1 为索引。表明了前面有多少个常量项

常量池次要寄存两大类常量:字面量(Literal)和符号援用(Symbolic References)

它蕴含了 class 文件构造及其子结构中援用的所有字符串常量、类或接口名、字段名和其余常量。

针对于类的全类目,咱们针对于 Demo 类,可能是 com.atguigu/test/Demo

那么针对于类的全限定名,咱们仅仅是把包名的 ”.” 替换成 ”/”,并且应用; 代表完结

那么针对于字段的名称、办法的名称,咱们称说为简略名称。指的是没有类型和参数润饰的办法或者字段名称,在 Demo 类里 add 办法的简略名称为 add、num 字段简略名称为 num

描述符的作用是 用来形容字段的数据类型、办法的参数列表(包含数量、类型以及程序)和返回值

依据描述符规定,根本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的 void 类型都用一个大写字符来示意,而对象类型则用字符 L 加对象的全限定名来示意

用描述符来形容办法时,依照先参数列表,后返回值的程序形容,参数列表依照参数的严格程序放在一组小括号“()”之内。

  • 如办法 java.lang. String toString()的描述符为()Ljava/lang/String;
  • 如办法 int abc(int[] x, int y)的描述符为([II)I

咱们能够采纳示例代码演示一下这些类型的形容

public class ArrayTest i
    public static void main(String[] args) {Object[] arr = new Object[10];
        system.out.println(arr);
        
        String[] arr1 = new String[10];
        system.out.println(arr1);
        
        long[][] arr2 = new long[10][];
        system.out.printin(arr2);
    }
}
// 运行后果如下:[Ljava.lang.Object;@1540e19d
[Ljava.lang.String;@677327b6
[[J@14ae5a5

虚拟机在 加载 Class 文件时才会进行动静链接 ,也就是说,Class 文件中不会保留各个办法和字段的最终内存布局信息。因而 这些字段和办法的符号援用不通过转换是无奈间接被虚拟机应用的

当虚拟机运行时,须要 从常量池中取得对声的符号援用,再在类加载过程中的解析阶段将其替换为间接援用,并翻译到具体的内存地址中

这里阐明下符号援用和间接援用的区别与关联:

  • 符号援用:符号援用以一组符号来形容所援用的指标,符号能够是任何模式的字面量,只有应用时能无歧义地定位到指标即可。
  • 符号援用与虚拟机实现的内存布局无关,援用的指标并不一定曾经加载到了内存中。
  • 间接援用:间接援用能够是 间接指向指标的指针、绝对偏移量或是一个能间接定位到指标的句柄
  • 间接援用是与虚拟机实现的内存布局相干的 ,同一个符号援用在不同虚拟机实例上翻译进去的间接援用个别不会雷同。如果 有了间接援用, 那阐明援用的指标必然己经存在于内存之中 了。

在字节码中应用第 1 个字节作为类型标记,用于确定该项的格局

咱们喜爱这个字节称为tag byte (标记字节、标签字节),具体什么类型看下图所示

接下来咱们针对于 Demo 类的字节码文件,进行解读看看第一个类型标记是什么

咱们能够看到第一个类型是 u1、u2、u2,占据 5 个字节,那么对应的占位是如下图

接下来咱们看仅接其后的第二个类型是什么?

咱们能够看到第二个类型是 u1、u2、u2,占据 5 个字节,那么对应的占位是如下图

接下来咱们看仅接其后的第三个类型是什么?

咱们能够看到第三个类型是 u1、u2,占据 3 个字节,那么对应的占位是如下图

接下来咱们看仅接其后的第四个类型也是 07,那么对应的占位是如下图

接下来咱们看仅接其后的第五个类型是什么?

这里字符串用两个字节来示意长度,那么对应的如下所示

咱们能够看到第五个类型对应的占位是如下图

接下来咱们看仅接其后的类型也是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型也是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型也是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,长度为 of(十进制:15)那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,长度为 18(十进制:24)那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,长度为 0a(十进制:10)那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 0c,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 0c,那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,长度为 16(十进制:22)那么对应的占位是如下图

接下来咱们看仅接其后的类型是 01,长度为 10(十进制:16)那么对应的占位是如下图

针对于这些咱们能够应用 javap 命令解析: javap -verbose Demo.class 或 jclasslib 工具会更好

常量池总结:

这 14 种表(或者常量项构造)的共同点是:表开始的第一位是一个 u1 类型的标记位(tag),代表以后这个常量项应用的是哪种表构造,即哪种常量类型。

在常量池列表中,CONSTANT_Utf8_info 常量项是一种应用改良过的 UTF- 8 编码格局,来存储诸如文字字符串、类或者接口的全限定名、字段或者办法的简略名称以及描述符等常量字符串信息。

这 14 种常量项构造还有一个特点是,其中 13 个常量项占用的字节固定,只有 CONSTANT_Utf8_info 占用字节不固定,其大小由 length 决定。为什么?

因为从常量池寄存的内容可知,其寄存的是字面量和符号援用,最终这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,比方你定义一个类,类名能够取长取短,所以在没编译前,大小不固定,编译后,通过 utf- 8 编码,就能够晓得其长度。

常量池能够了解为 Class 文件之中的资源仓库,它是 Class 文件构造中与其余我的项目关联最多的数据类型前面的很多数据类型都会指向此处),也是占用 class 文件空间最大的数据我的项目之一。

常量池中为什么要蕴含这些内容?

Java 代码在进行 Javac 编译的时候,并不像 c 和 C ++ 那样有“连贯”这一步骤(翻译机器指令)

而是在虚拟机加载 Class 文付的时候进行动静链接。也就是说,在 Class 文件中不会保留各个办法、字段的最终内存布局信息 ,因而 这些字段、办法的符号援用不通过运行期转换的话无奈失去真正的内存入口地址,也就无奈间接被虚拟机应用

当虚拟机运行时,须要从常量池取得对应的符号援用再在类创立时或运行时解析、翻译到具体的内存地址之中。对于类的创立和动静链接的内容,在虚拟机类加载过程时再进行具体解说

拜访标识

================================

在常量池后,紧跟着拜访标记。该标记应用 两个字节示意 用于辨认一些类或者接口档次的访问信息

常见信息个别包含:

  • 这个 Class 是类还是接口;
  • 是否定义为 public 类型;
  • 是否定义为 abstract 类型;
  • 如果是类的话,是否被申明为 final。

具体还有其余的各种拜访标记如下所示:

个别类的 拜访权限通常为 ACC_ 结尾的常量 ,每一种类型的示意都是通过 设置拜访标记的 32 位中的特定位 来实现的。比方:若是 public final 的类,则该标记为ACC_PUBLIC | ACC_FINAL

应用 ACC_SUPER 能够让类更精确地定位到父类的办法 super.method(), 古代编译器都会设置应用这个

咱们能够看看下面的 Demo 的字节码文件,是什么信息?

那么咱们下面的表格里没有 21,那么这是因为 21 是加起来后的数值,能够看看具体有哪些组成?

带有 ACC_INTERFACE 标记的 class 文件示意的是接口而不是类,反之则示意的是类而不是接口。

如果一个 class 文件被设置了 ACC_INTERFACE 标记,那么 同时也得设置 ACC_ABSTRACT标记。同时它 不能再设置 ACC_FINAL、ACC_SUPER 或 ACC_ENUM标记。

如果没有设置 ACC_INTERFACE 标记,那么这个 class 文件能够具备上表中 除 ACC_ANNOTATTON 外 的其余所有标记。当然,ACC_FINALACC_ABSTRACT 这类互斥的标记除外。这两个标记不得同时设置。

ACC_SUPER标记用于确定类或接口外面的 invokespecial 指令应用的是哪一种执行语义

针对 Java 虚拟机指令集的编译器都该当设置这个标记。对于 Java SE 8 及后续版本来说,无论 class 文件中这个标记的理论值是什么,也不论 class 文件的版本号是多少

Java 虚拟机都认为每个 class 文件均设置了 ACC_SUPER 标记

ACC_SUPER标记是为了向后兼容由旧 Java 编译器所编译的代码而设计的。目前的 ACC_SUPER 标记在由 JDK 1.0.2 之前的编译器所生成的 access_flags 中是没有确定含意 的,如果设置了该标记,那么 oracle 的 Java 虚拟机实现会将其疏忽

ACC_SYNTHETIC标记意味着该类或接口是由编译器生成的,而不是由源代码生成的

注解类型必须设置 ACC_ANNOTATION 标记。如果设置了 ACC_ANNOTATION 标记,那么也必须设置 ACC_INTERFACE 标记、ACC_ENUM标记表明该类或其父类为枚举类型。

类索引、父类索引、接口索引汇合

================================

在拜访标记后,会指定该类的类别、父类类别以及实现的接口,格局如下:

这三项数据来确定这个类的继承关系。

  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名

因为 Java 语言不容许多重继承,所以父类索引只有一个,除了 java.lang .object 之外,所有的 Java 类都有父类。因而除了 java.lang.0bject 外,所有 Java 类的父类索引都不为 0。

  • 接口索引汇合就用来形容这个类实现了哪些接口

这些被实现的接口将按 implements 语句(如果这个类自身是一个接口,则该当是 extends 语句)后的接口程序从左到右排列在接口索引汇合中

接下来咱们看看 Demo 类的字节码文件外面是哪些信息?

this_class(类索引)

2 字节无符号整数,指向常量池的索引。它提供了类的全限定名如:com/atguigu/java1/Demo

this_class 的值必须是对常量池表中某项的一个无效索引值。常量池在这个索引处的成员必须为 CONSTANT_Class_info 类型构造体,该构造体示意这个 class 文件所定义的类或接口。

super_class(父类索引)

2 字节无符号整数,指向常量池的索引。它提供了以后类的父类的全限定名。

如果咱们没有继承任何类,其默认继承的是 java/lang/0bject 类。同时,因为 Java 不反对多继承,所以其父类只有一个, 以及.superclass 指向的父类不能是 final

interfaces

指向常量池索引汇合,它提供了一个符号援用到所有已实现的接口

因为一个类能够实现多个接口,因而须要以数组模式保留多个接口的索引,示意接口的每个索引也是一个指向常量池的 CONSTANT_Class(当然这里就必须是接口,而不是类)。

interfaces_count(接口计数器)

interfaces_count 项的值示意以后类或接口的间接超接口数量。

interfaces [](接口索引汇合)

interfaces[]中每个成员的值必须是对常量池表中某项的无效索引值,长度为 interfaces_count

每个成员 interfaces[i]必须为 CONSTANT_Class_info 构造,其中 0 <= i <interfaces_count

在 interfaces[]中,各成员所示意的接口程序和对应的源代码中给定的接口程序(从左至右)一样,即 interfaces[0]对应的是源代码中最右边的接口。

字段表汇合(fields)

================================

用于形容接口或类中申明的变量

字段 (field) 包含类级变量以及实例级变量,然而不包含办法外部、代码块外部申明的局部变量

字段叫什么名字、字段被定义为什么数据类型,这些都是无奈固定的,只能援用常量池中的常量来形容

它指向常量池索引汇合,它形容了每个字段的残缺信息。比方 字段的标识符、拜访修饰符(public、private 或 protected)、是类变量还是实例变量 (static 修饰符)、是否是常量(final 修饰符) 等。

注意事项:

字段表汇合中 不会列出从父类或者实现的接口中继承而来的字段 ,但有 可能列出本来 Java 代码之中不存在的字段 。譬如在内部类中为了放弃对外部类的拜访性,会 主动增加指向外部类实例的字段

在 Java 语言中 字段是无奈重载的,两个字段的数据类型、修饰符不论是否雷同,都必须应用不一样的名称,然而对于字节码来讲,如果两个字段的描述符不统一,那字段重名就是非法的。

fields_count(字段计数器)

fields_count 的值示意 以后 class 文件 fields 表的成员个数。应用两个字节来示意

fields 表中每个成员都是一个 field_info 构造,用于示意 该类或接口所申明的所有类字段或者实例字段,不包含办法外部申明的变量,也不包含从父类或父接口继承的那些字段

fields [](字段表)

每个成员都必须是一个 fields_infc 构造的数据项,用天示意以后类或接口中某个字段的残缺形容

一个字段的信息包含如下这些信息。这些信息中,各个修饰符都是布尔值,要么有要么没有

  • 作用域(public、private、protected 修饰符)>
  • 是实例变量还是类变量(static 修饰符)
  • 可变性(final)
  • 并发可见性(volatile 修饰符,是否强制从主内存读写)
  • 可否序列化(transient 修饰符)
  • 字段数据类型(根本数据类型、对象、数组)
  • 字段名称

字段表作为一个表,同样也有它本人的构造,如下图:

字段表的拜访标识

咱们晓得字段能够被各种关键字去润饰,比方:作用域修饰符(public、private、protected)、static 修饰符、final 修饰符、volatile 修饰符 等等。

因而,其可像类的拜访标记那样,应用一些标记来标记字段。字段的拜访标记有如下这些:

字段名索引

依据字段名索引的值, 查问常量池中的指定索引项即可

接下来咱们剖析一下 Demo 类里的对应字段标识是哪个?

指向的是常量池第 5,那么对应的常量池十进制也是 05,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的字节码是是什么

描述符索引

描述符的作用是用来形容字段的数据类型、办法的参数列表(包含数量、类型以及程序)和返回值。

依据描述符规定,根本数据类型 (byte,char ,double ,float ,int ,long,short , boolean) 及代表无返回值的 void 类型都用一个大写字符来示意,而对象则用字符 L 加对象的全限定名来示意,如下所示:

接下来咱们剖析一下 Demo 类里的对应描述符是哪个?

指向的是常量池第 6,那么对应的常量池十进制也是 73,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

属性计数器

若咱们以后字段有属性的话,就会记录个数以及形容,咱们剖析一下 Demo 类有什么?00 代表没有

属性表汇合

一个字段还可能领有一些属性,用于存储更多的额定信息。比方初始化值、一些正文信息等。

属性个数寄存在 attribute_count 中,属性具体内容寄存在 attributes 数组中

以常量属性为例,构造为:
ConstantValue_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

阐明: 对于常量属性而言,attribute_ 1ength 值恒为 2

咱们能够批改 Dmeo 类里的 num 值,将它批改为 final 润饰,并且查看对应的信息

接下来咱们对该常量属性的构造进行剖析看看,如下图所示

办法表汇合(methods)

================================

指向常量池索引汇合,它残缺形容了每个办法的签名。

在字节码文件中,每一个 method_info 项都对应着一个类或者接口中的办法信息。比方办法的拜访修饰符(public,private 或 protected), 办法的返回值类型以及办法的参数信息等。

如果这个办法不是形象的或者不是 native 的,那么字节码中会体现进去。

一方面 methods 表只 形容以后类或接口中申明的办法,不包含从父类或父接口继承的办法

一方面 methods 表 有可能会呈现由编译器主动增加的办法,比拟典型的便是

编译器产生的办法信息 (比方: 类(接口) 初始化办法 <clinit>()和实例初始化办法 <init>())。

注意事项:

在 Java 语言中要重载 (Overload) 一个办法,除了要 与原办法具备雷同的简略名称 之外,还要求 必须领有一个与原办法不同的特色签名

特色签名就是 一个办法中各个参数在常量池中的字段符号援用的汇合 ,也就是 因为返回值不会蕴含在特色签名之中,因而 Java 语言里无奈仅仅依附返回值的不同来对一个已有办法进行重载

咱们看以下的示例代码就晓得了,无奈依据返回值来辨别重载

public  void  method1(int num){


}

public  int  method1(int num){}

但在 Class 文件格式中,特色签名的范畴更大一些,只有描述符不是完全一致的两个办法就能够共存

如果两个办法有雷同的名称和特色签名,但返回值不同,那么也是能够非法共存于同一个 class 文件

也就是说只管 Java 语法标准并不容许在一个类或者接口中申明多个办法签名雷同的办法,然而和 ava 语法标准相同, 字节码文件中却恰好容许寄存多个办法签名雷同的办法 ,惟一的条件就是这些 办法之间的返回值不能雷同

methods_count(办法计数器)

methods_count 的值示意以后 class 文件 methods 表的成员个数, 应用两个字节来示意

methods 表中每个成员都是一个 method info 构造

接下来咱们剖析一下 Demo 类里的对应是哪个?

methods [](办法表)

methods 表中的每个成员都必须是一个 method_info 构造,具体可看下图所示:

method_info 构造能够示意类和接口中定义的所有办法,包含实例办法、类办法、实例初始化办法和类或接口初始化办法,个别用于示意以后类或接口中某个办法的残缺形容。

如果某个 method_info 构造的 access_flags 项既没有设置 ACC_NATTV 标记也没有设置 ACC_ABSTRACT 标记,那么该构造中也应蕴含实现这个办法所用的 Java 虚拟机指令

办法表拜访标记

跟字段表一样,办法表的标记有局部雷同,局部则不同,办法表的具体拜访标记如下:

依据咱们后面办法表的 method_info 构造,咱们先来看第一个拜访标记

该对应的是 01,那么与咱们办法表的具体拜访标记是 public

办法名索引

依据办法名索引的值, 查问常量池中的指定索引项即可

依据咱们后面办法表的 method_info 构造,咱们先来看第二个办法名索引

指向的是常量池第 7,那么对应的常量池的地位,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

描述符索引

描述符的作用是用来形容办法的参数列表(包含数量、类型以及程序)和返回值

及代表无返回值的 void 类型都用一个大写字符来示意

依据咱们后面办法表的 method_info 构造,咱们先来看第三个描述符索引

指向的是常量池第 8,那么对应的常量池的地位,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

办法属性计数器

依据咱们后面办法表的 method_info 构造,咱们先来看第四个属性计数器

办法属性表

属性表的每个项的值必须是 attribute_info 构造

属性表的构造比拟灵便,各种不同的属性只有满足以下构造即可

办法属类型

方属性表实际上能够有很多类型,Java8 外面定义了 23 种属性,上面这些是虚拟机中预约义的属性:


办法属性名索引

依据咱们后面办法属性表的构造,咱们来解读看看办法属性名索引

指向的是常量池第 9,那么对应的常量池的地位,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

针对于 Code 属性,它代表的是寄存办法体外面的代码。然而并非所有办法表都有 Code 属性

像接口或者形象办法,他们没有具体的办法体因而也就不会有 code 属性

Code 属性的机构图,如下图所示

能够看到 Code 属性表的前两项跟属性表是统一的,即 Code 属性表遵循属性表的构造,前面那些则是他自定义的构造。

Code 的属性长度

依据咱们后面 Code 属性的表的构,咱们来解读看看 Code 的属性长度

指向的是 38,那么对应的十进制是 56,那么就是如下这些地位

Code 的操作数栈深度的最大值

依据咱们后面 Code 属性的表的构,咱们来解读看看 Code 的操作数栈深度的最大值

Code 的局部变量表所需的存续空间

依据咱们后面 Code 属性的表的构,咱们来解读看看 Code 的局部变量表所需的存续空间

Code 的字节码指令长度

依据咱们后面 Code 属性的表的构,咱们来解读看看 Code 的字节码指令长度

指向的是 0a,那么对应的十进制是 10,那么就是如下这些地位

Code 的异样表的长度

依据咱们后面 Code 属性的表的构,咱们来解读看看 Code 的异样表的长度

Code 的属性的汇合计数器

依据咱们后面 Code 属性的表的构,咱们来解读看看 Code 的属性的汇合计数器

Code 的属性名索引

依据咱们后面 Code 属性的表的构,咱们来解读看看 Code 的属性名索引

指向的是 0a,那么对应的十进制是 10,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

LineNumberTable 属性

LineNumberTable 属性是可选变长属性,位于 Code 构造的属性表

LineNumberTable 属性是用来 形容 Java 源码行号与字节码行号之间的对应关系

这个属性能够用来 在调试的时候定位代码执行的行数

LineNumberTable 属性表构造:

LineNumberTable_attribute {
    u2 atribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {
        u2 start_pc;
        u2 line_number;
    }line_number_table[line_number_table_length];    
}
  • start_pc, 即字节码行号
  • line_number,即 Java 源代码行号

在 Code 属性的属性表中 LineNumberTable 属性能够依照任意程序呈现

此外多个 LineNumberTable 属性能够独特示意一个行号在源文件中示意的内容

即 LineNumberTable 属性不须要与源文件的行一一对应。

依据咱们后面 LineNumberTable 属性构造,咱们来解读看看属性长度

指向的是 0a,那么对应的十进制是 10,那么就是如下这些地位

针对于这十个,咱们再剖析剖析后面 LineNumberTable 属性构造的 line_number 长度

针对于这十个,咱们再剖析剖析后面 LineNumberTable 属性构造的字节码行号

针对于这十个,咱们再剖析剖析后面 LineNumberTable 属性构造的 Java 源代码行号

因为 LineNumberTable 属性构造的 line_number 长度为 2,所以就有二组,看图所示

如果咱们应用软件查看 Demo 类的字节码剖析的话,就能够看到上面所图所示的样子

接下来咱们看 code 属性的第二个属性

指向的是 0b,那么对应的十进制是 11,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

LocalVariableTable 属性

LocalVariableTable 是可选变长属性,位于 Code 属性的属性表中。它被调试器用于确定 办法在执行过程中局部变量的信息

在 Code 属性的属性表中,LocalVariableTable 属性能够依照任意程序呈现

Code 属性中的 每个局部变量最多只能有一个 LocalVariableTable 属性

LocalVariableTable 属性表构造:

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    { 
        u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}
  • start pc + length 示意这个变量在字节码中的生命周期起始和完结的偏移地位(this 生命周期从头 0 到结尾 10
  • index 就是这个变量在局部变量表中的槽位(槽位可复用)
  • name 就是变量名称
  • Descriptor 示意局部变量类型形容

依据咱们后面 LineNumberTable 属性构造,咱们来解读看看属性长度

针对于这 LocalVariableTable,咱们再剖析分析属性构造的 table_number 长度

针对于这 LocalVariableTable,咱们再剖析分析属性构造的字节码行号

针对于这 LocalVariableTable,咱们再剖析分析属性构造的长度

针对于这 LocalVariableTable,咱们再剖析分析属性构造的变量名称索引

指向的是 0C,那么对应的十进制是 12,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

针对于这 LocalVariableTable,咱们再剖析分析属性构造的描述符索引

指向的是 0C,那么对应的十进制是 13,那么就是如下这些地位

咱们能够应用 Binary Viewer 软件查看对应的 ASCII 码是是什么

对应咱们的 Demo 类的字节码文件剖析能够看到如下图所示所示

属性表汇合(attributes)

================================

办法表汇合之后的属性表汇合,指的是 class 文件所携带的辅助信息.

比方该 class 文件的源文件的名称。以及任何带有 RetentionPolicy.CLASS 或者 RetentionPolicy.RUNTINE 的注解。

这类信息通常被用于Java 虚拟机的验证和运行,以及 Java 程序的调试,个别毋庸深刻理解

此外,字段表、办法表都能够有本人的属性表(比方常量)。用于形容某些场景专有的信息。

属性表汇合的限度没有那么严格,不再要求各个属性表具备严格的程序。

并且 只有不与已有的属性名反复,任何人实现的编译器都能够向属性表中写入本人定义的属性信息 ,但 Java 虚拟机运行时会 疏忽掉它不意识 的属性。

正文完
 0