解读字节码之旅


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

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 againException 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虚拟机运行时会疏忽掉它不意识的属性。