JVM系列文章(三):Class文件内容解析
作为一个程序猿,只知道怎么用是远远不够的。起码,你须要知道为什么能够这么用。即我们所谓底层的东西。
那究竟什么是底层呢?我认为这不能一概而论。以我如今的知识水平而言:对于Web开发人员,TCP/IP、HTTP等等协议可能就是底层;对于C、C++程序猿。内存、指针等等可能就是底层的东西。那对于Java开发人员。你的Java代码执行所在的JVM可能就是你所须要去了解、理解的东西。
我会在接下来的一段时间,和读者您一起去学习JVM。全部内容均參考自《深入理解Java虚拟机:JVM高级特性与最佳实践》(第二版),感谢作者。
系列文章第一篇:JVM系列文章(一):Java内存区域分析。
系列文章第二篇:JVM系列文章(二):垃圾回收机制。
一、概述
不论什么一个Class文件都相应唯一一个类或接口的定义信息,可是不是全部的类或接口都得定义在文件里(它们也能够通过类载入器直接生成)。
Class文件是一组以8位字节为基础单位的二进制流。各个数据项严格按顺序排列,没有不论什么分隔符。
Class文件格式採用一种类似于C语言结构体的伪结构来存储数据。这样的伪结构仅仅有两种数据类型:无符号数和表。
无符号数:是基本数据类型。以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数,能够用来描写叙述数字、索引引用、数量值或者依照UTF-8编码构成的字符串值。
表:由多个无符号数或者其它表作为数据项构成的复合数据类型。全部表都习惯性地以“_info”结尾。整个Class文件本质上就是一张表,例如以下所看到的:
类型 |
名称 |
数量 |
u4 |
magic |
1 |
u2 |
minor_version |
1 |
u2 |
major_version |
1 |
u2 |
constant_pool_count |
1 |
cp_info |
constant_pool |
constant_pool_count-1 |
u2 |
access_flags |
1 |
u2 |
this_class |
1 |
u2 |
super_class |
1 |
u2 |
interfaces_count |
1 |
u2 |
interfaces |
interfaces_count |
u2 |
fields_count |
1 |
field_info |
fields |
fields_count |
u2 |
methods_count |
1 |
method_info |
methods |
methods_count |
u2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
二、各个字段具体解释
package com.test; public class Test { private int m; public int getM(){ return m + 1; } }
1.魔数
非常多文件存储标准中都使用魔数来进行身份识别。譬如图片格式gif、jpeg等。使用魔数而不是拓展名来进行识别主要是基于安全方面的考虑,由于文件拓展格式能够任意修改。
2.版本
3.常量池
类型 |
简单介绍 |
项目 |
类型 |
描写叙述 |
CONSTANT_Utf8_info |
utf-8缩略编码字符串 |
tag |
u1 |
值为1 |
length |
u2 |
utf-8缩略编码字符串占用字节数 |
||
bytes |
u1 |
长度为length的utf-8缩略编码字符串 |
||
CONSTANT_Integer_info |
整形字面量 |
tag |
u1 |
值为3 |
bytes |
u4 |
依照高位在前储存的int值 |
||
CONSTANT_Float_info |
浮点型字面量 |
tag |
u1 |
值为4 |
bytes |
u4 |
依照高位在前储存的float值 |
||
CONSTANT_Long_info |
长整型字面量 |
tag |
u1 |
值为5 |
bytes |
u8 |
依照高位在前储存的long值 |
||
CONSTANT_Double_info |
双精度浮点型字面量 |
tag |
u1 |
值为6 |
bytes |
u8 |
依照高位在前储存的double值 |
||
CONSTANT_Class_info |
类或接口的符号引用 |
tag |
u1 |
值为7 |
index |
u2 |
指向全限定名常量项的索引 |
||
CONSTANT_String_info |
字符串类型字面量 |
tag |
u1 |
值为8 |
index |
u2 |
指向字符串字面量的索引 |
||
CONSTANT_Fieldref_info |
字段的符号引用 |
tag |
u1 |
值为9 |
index |
u2 |
指向声明字段的类或接口描写叙述符CONSTANT_Class_info的索引项 |
||
index |
u2 |
指向字段描写叙述符CONSTANT_NameAndType_info的索引项 |
||
CONSTANT_Methodref_info |
类中方法的符号引用 |
tag |
u1 |
值为10 |
index |
u2 |
指向声明方法的类描写叙述符CONSTANT_Class_info的索引项 |
||
index |
u2 |
指向名称及类型描写叙述符CONSTANT_NameAndType_info的索引项 |
||
CONSTANT_InterfaceMethodref_info |
接口中方法的符号引用 |
tag |
u1 |
值为11 |
index |
u2 |
指向声明方法的接口描写叙述符CONSTANT_Class_info的索引项 |
||
index |
u2 |
指向名称及类型描写叙述符CONSTANT_NameAndType_info的索引项 |
||
CONSTANT_NameAndType_info |
字段或方法的部分符号引用 |
tag |
u1 |
值为12 |
index |
u2 |
指向该字段或方法名称常量项的索引 |
||
index |
u2 |
指向该字段或方法描写叙述符常量项的索引 |
首先来看常量池中的第一项常量,其标志位为0x07,是一个CONSTANT_Class_info类型常量。此类型常量代表一个类或接口的符号引用。依据其数据结构,接下来2位字节用来保存一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型的常量,此常量代表了这个类或接口的全限定名,索引值为0x0002。即指向了常量池中的第二项常量。
第二项常量标志位为0x01。确实是一个CONSTANT_Utf8_info类型的常量。依据其数据结构。接下来2个字节用来保存utf-8缩略编码字符串长度,其值为0x000D,转化为十进制为13,即接下来的13个字节为一个utf-8缩略编码的字符串。为com/test/Test。能够看到正好是測试类的全限定名。
4.訪问标志
志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
是否为public类型 |
ACC_FINAL |
0x0010 |
是否被声明为final,仅仅有类可设置 |
ACC_SUPER |
0x0020 |
是否同意使用invokespecial字节码指令,JDK1.2以后编译出来的类这个标志为真 |
ACC_INTERFACE |
0x0200 |
标识这是一个接口 |
ACC_ABSTRACT |
0x0400 |
是否为abstract类型,对于接口和抽象类,此标志为真。其他类为假 |
ACC_SYNTHETIC |
0x1000 |
标识别这个类并不是由用户代码产生 |
ACC_ANNOTATION |
0x2000 |
标识这是一个注解 |
ACC_ENUM |
0x4000 |
标识这是一个枚举 |
依据上面的表格,測试类的訪问标志0x0021= 0x0001 | 0x0020 =ACC_PUBLIC | ACC_SUPER
5.类索引、父类索引和接口索引集合
Class文件里由这3项数据来确定这个类的继承关系
this_class:类索引,用于确定这个类的全限定名,占2字节
super_class:父类索引。用于确定这个类父类的全限定名(Java语言不同意多重继承,故父类索引仅仅有一个。
除了java.lang.Object类之外全部类都有父类,故除了java.lang.Object类之外,全部类该字段值都不为0),占2字节
interfaces_count:接口索引计数器。占2字节。
假设该类没有实现不论什么接口。则该计数器值为0,而且后面的接口的索引集合将不占用不论什么字节。
interfaces:接口索引集合,一组u2类型数据的集合。用来描写叙述这个类实现了哪些接口。这些被实现的接口将按implements语句(假设该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中
this_class、super_class与interfaces中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量。通过这个常量中保存的索引值能够找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串
this_class的值为0x0001,即常量池中第一个常量,super_class的值为0x0003,即常量池中的第三个常量,interfaces_counts的值为0x0000,故接口索引集合大小为0
6.字段表集合
fields_count:字段表计数器。即字段表集合中的字段表数据个数。占2字节,其值为0x0001,即仅仅有一个字段表数据。也就是測试类中仅仅包括一个变量(不算方法内部变量)
fields:字段表集合,一组字段表类型数据的集合。字段表用于描写叙述接口或类中声明的变量。包含类级别(static)和实例级别变量,不包含在方法内部声明的变量
在Java中一般通过例如以下几项描写叙述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。
在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示,字段表格式例如以下表所看到的:
类型 |
名称 |
数量 |
u2 |
access_flags |
1 |
u2 |
name_index |
1 |
u2 |
descriptor_index |
1 |
u2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
字段修饰符放在access_flags中,占2字节,其值为0x0002,可见这个字段由private修饰,与訪问标志位十分相似
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
字段是否为public |
ACC_PRIVATE |
0x0002 |
字段是否为private |
ACC_PROTECTED |
0x0004 |
字段是否为protected |
ACC_STATIC |
0x0008 |
字段是否为static |
ACC_FINAL |
0x0010 |
字段是否为final |
ACC_VOLATILE |
0x0040 |
字段是否为volatile |
ACC_TRANSIENT |
0x0080 |
字段是否为transient |
ACC_SYNTHETIC |
0x1000 |
字段是否为编译器自己主动产生 |
ACC_ENUM |
0x4000 |
字段是否为enum |
7.方法表集合
methods_count:方法表计数器,即方法表集合中的方法表数据个数。
占2字节,其值为0x0002,即測试类中有2个方法(还自己主动添加了一个构造函数)
methods:方法表集合,一组方法表类型数据的集合。
方法表结构和字段表结构一样:
类型 |
名称 |
数量 |
u2 |
access_flags |
1 |
u2 |
name_index |
1 |
u2 |
descriptor_index |
1 |
u2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
数据项的含义很相似。仅在訪问标志位和属性表集合中的可选项上有稍微不同
因为ACC_VOLATILE标志和ACC_TRANSIENT标志不能修饰方法,所以access_flags中不包括这两项,同一时候添加ACC_SYNCHRONIZED标志、ACC_NATIVE标志、ACC_STRICTFP标志和ACC_ABSTRACT标志
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
字段是否为public |
ACC_PRIVATE |
0x0002 |
字段是否为private |
ACC_PROTECTED |
0x0004 |
字段是否为protected |
ACC_STATIC |
0x0008 |
字段是否为static |
ACC_FINAL |
0x0010 |
字段是否为final |
ACC_SYNCHRONIZED |
0x0020 |
字段是否为synchronized |
ACC_BRIDGE |
0x0040 |
方法是否是由编译器产生的桥接方法 |
ACC_VARARGS |
0x0080 |
方法是否接受不定參数 |
ACC_NATIVE |
0x0100 |
字段是否为native |
ACC_ABSTRACT |
0x0400 |
字段是否为abstract |
ACC_STRICTFP |
0x0800 |
字段是否为strictfp |
ACC_SYNTHETIC |
0x1000 |
字段是否为编译器自己主动产生 |
第一个方法(由编译器自己主动加入的默认构造方法):
access_flags为0x0001,即public;name_index为0x0007。即常量池中第7个常量;descriptor_index为0x0008,即常量池中第8个常量
- const #7 = Asciz <init>;
- const #8 = Asciz ()V;
接下来2个字节为属性计数器,其值为0x0001,说明这种方法的属性表集合中有一个属性。属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x0000002F,表示Code属性值的字节长度为47。接下来2位为0x0001。表示该方法的操作数栈的深度最大值为1。接下来2位依旧为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000005。则紧接着的5个字节0x2AB7000AB1为该方法编译后生成的字节码指令(各字节相应的指令不介绍了,可查询虚拟机字节码指令表)。接下来2个字节为0x0000,说明Code属性异常表集合为空。
接下来2个字节为0x0002,说明Code属性带有2个属性,那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。接下来4位为0x00000006。表示LineNumberTable属性值所占字节长度为6。接下来2位为0x0001,即该line_number_table中仅仅有一个line_number_info表,start_pc为0x0000,line_number为0x0003,LineNumberTable属性结束。
接下来2位0x000D为Code属性第二个属性的属性名。指向常量池中第13个常量:LocalVariableTable。
该属性值所占的字节长度为0x0000000C=12。接下来2位为0x0001,说明local_variable_table中仅仅有一个local_variable_info表。依照local_variable_info表结构,start_pc为0x0000。length为0x0005,name_index为0x000E。指向常量池中第14个常量:this。descriptor_index为0x000F,指向常量池中第15个常量:Lcom/test/Test;。index为0x0000。
第一个方法结束
第二个方法:
access_flags为0x0001,即public。name_index为0x0010。即常量池中第16个常量。descriptor_index为0x0011,即常量池中第17个常量
- const #16 = Asciz getM;
- const #17 = Asciz ()I;
接下来2个字节为属性计数器,其值为0x0001,说明这种方法有一个方法属性,属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x00000031。表示Code属性值的字节长度为49。接下来2位为0x0002,表示该方法的操作数栈的深度最大值为2。接下来2位为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000007,则紧接着的7个字节0x2AB400120460AC为该方法编译后生成的字节码指令。
接下来2个字节为0x0000。说明Code属性异常表集合为空。
接下来2个字节为0x0002,说明Code属性带有2个属性。那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。
接下来4位为0x00000006。表示LineNumberTable属性值所占字节长度为6。
接下来2位为0x0001。即该line_number_table中仅仅有一个line_number_info表,start_pc为0x0000。line_number为0x0007,LineNumberTable属性结束。
和第一个方法的LocalVariableTable属性基本同样,唯一的差别是局部变量this的作用范围覆盖的长度为7而不是5,第二个方法结束
假设子类没有重写父类的方法,方法表集合中就不会出现父类方法的信息。有可能会出现由编译器自己主动加入的方法(如:<init>。实例类构造器)
在Java语言中,重载一个方法除了要求和原方法拥有同样的简单名称外。还要求必须拥有一个与原方法不同的特征签名(方法參数集合),因为特征签名不包括返回值,故Java语言中不能只依靠返回值的不同对一个已有的方法重载;可是在Class文件格式中。特征签名即为方法描写叙述符,只要是描写叙述符不全然同样的2个方法也能够合法共存。即2个除了返回值不同之外全然同样的方法在Class文件里也能够合法共存
javap工具在后半部分会列出分析完毕的方法(能够看到和我们的分析结果是一样的):
- d:\>javap -verbose Test
- ......
- {
- public com.test.Test();
- Code:
- Stack=1, Locals=1, Args_size=1
- 0: aload_0
- 1: invokespecial #10; //Method java/lang/Object."<init>":()V
- 4: return
- LineNumberTable:
- line 3: 0
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcom/test/Test;
- public int getM();
- Code:
- Stack=2, Locals=1, Args_size=1
- 0: aload_0
- 1: getfield #18; //Field m:I
- 4: iconst_1
- 5: iadd
- 6: ireturn
- LineNumberTable:
- line 7: 0
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 7 0 this Lcom/test/Test;
- }
8.属性表集合
在Class文件、属性表、方法表中都能够包括自己的属性表集合。用于描写叙述某些场景的专有信息
与Class文件里其他数据项对长度、顺序、格式的严格要求不同,属性表集合不要求当中包括的属性表具有严格的顺序,而且仅仅要属性的名称不与已有的属性名称反复。不论什么人实现的编译器可以向属性表中写入自定义的属性信息。虚拟机在执行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中提前定义了虚拟机实现必须可以识别的9项属性:
属性名称 |
使用位置 |
含义 |
Code |
方法表 |
Java代码编译成的字节码指令 |
ConstantValue |
字段表 |
finalkeyword定义的常量值 |
Deprecated |
类文件、字段表、方法表 |
被声明为deprecated的方法和字段 |
Exceptions |
方法表 |
方法抛出的异常 |
InnerClasses |
类文件 |
内部类列表 |
LineNumberTale |
Code属性 |
Java源代码的行号与字节码指令的相应关系 |
LocalVariableTable |
Code属性 |
方法的局部变量描写叙述 |
SourceFile |
类文件 |
源文件名 |
Synthetic |
类文件、方法表、字段表 |
标识方法或字段是由编译器自己主动生成的 |
每种属性均有各自的表结构。
这9种表结构有一个共同的特点,即均由一个u2类型的属性名称開始,能够通过这个属性名称来判段属性的类型
Code属性:Java程序方法体中的代码经过Javac编译器处理后,终于变为字节码指令存储在Code属性中。当然不是全部的方法都必须有这个属性(接口中的方法或抽象方法就不存在Code属性)。Code属性表结构例如以下:
类型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
max_stack |
1 |
u2 |
max_locals |
1 |
u4 |
code_length |
1 |
u1 |
code |
code_length |
u2 |
exception_table_length |
1 |
exception_info |
exception_table |
exception_table_length |
u2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
max_stack:操作数栈深度最大值,在方法执行的不论什么时刻,操作数栈深度都不会超过这个值。虚拟机执行时依据这个值来分配栈帧的操作数栈深度
max_locals:局部变量表所需存储空间,单位为Slot(參见备注四)。
并非全部局部变量占用的Slot之和,当一个局部变量的生命周期结束后。其所占用的Slot将分配给其他依旧存活的局部变量使用。按此方式计算出方法执行时局部变量表所需的存储空间
code_length和code:用来存放Java源程序编译后生成的字节码指令。code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。
每个指令是一个u1类型的单字节,当虚拟机读到code中的一个字节码(一个字节能表示256种指令,Java虚拟机规范定义了当中约200个编码相应的指令)。就能够推断出该字节码代表的指令。指令后面是否带有參数,參数该怎样解释。尽管code_length占4个字节,可是Java虚拟机规范中限制一个方法不能超过65535条字节码指令。假设超过。Javac将拒绝编译
ConstantValue属性:通知虚拟机自己主动为静态变量赋值,仅仅有被statickeyword修饰的变量(类变量)才干够使用这项属性。
其结构例如以下:
类型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
constantvalue_index |
1 |
能够看出ConstantValue属性是一个定长属性,当中attribute_length的值固定为0x00000002,constantvalue_index为一常量池字面量类型常量索引(Class文件格式的常量类型中仅仅有与基本类型和字符串类型相相应的字面量常量,所以ConstantValue属性仅仅支持基本类型和字符串类型)
对非static类型变量(实例变量。如:int a = 123;)的赋值是在实例构造器<init>方法中进行的
对类变量(如:static int a = 123;)的赋值有2种选择,在类构造器<clinit>方法中或使用ConstantValue属性。当前Javac编译器的选择是:假设变量同一时候被static和final修饰(虚拟机规范仅仅要求有ConstantValue属性的字段必须设置ACC_STATIC标志,对finalkeyword的要求是Javac编译器自己增加的要求),而且该变量的数据类型为基本类型或字符串类型。就生成ConstantValue属性进行初始化;否则在类构造器<clinit>方法中进行初始化
Exceptions属性:列举出方法中可能抛出的受查异常(即方法描写叙述时throwskeyword后列出的异常),与Code属性平级,与Code属性包括的异常表不同,其结构为:
类型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
number_of_exceptions |
1 |
u2 |
exception_index_table |
number_of_exceptions |
number_of_exceptions表示可能抛出number_of_exceptions种受查异常
exception_index_table为异常索引集合,一组u2类型exception_index的集合,每个exception_index为一个指向常量池中一CONSTANT_Class_info型常量的索引,代表该受查异常的类型
InnerClasses属性:该属性用于记录内部类和宿主类之间的关系。
假设一个类中定义了内部类。编译器将会为这个类与这个类包括的内部类生成InnerClasses属性,结构为:
类型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
number_of_classes |
1 |
inner_classes_info |
inner_classes |
number_of_classes |
inner_classes为内部类表集合。一组内部类表类型数据的集合,number_of_classes即为集合中内部类表类型数据的个数
每个内部类的信息都由一个inner_classes_info表来描写叙述,inner_classes_info表结构例如以下:
类型 |
名称 |
数量 |
u2 |
inner_class_info_index |
1 |
u2 |
outer_class_info_index |
1 |
u2 |
inner_name_index |
1 |
u2 |
inner_name_access_flags |
1 |
inner_class_info_index和outer_class_info_index指向常量池中CONSTANT_Class_info类型常量索引,该CONSTANT_Class_info类型常量指向常量池中CONSTANT_Utf8_info类型常量。分别为内部类的全限定名和宿主类的全限定名
inner_name_index指向常量池中CONSTANT_Utf8_info类型常量的索引。为内部类名称,假设为匿名内部类。则该值为0
inner_name_access_flags类似于access_flags。是内部类的訪问标志
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
内部类是否为public |
ACC_PRIVATE |
0x0002 |
内部类是否为private |
ACC_PROTECTED |
0x0004 |
内部类是否为protected |
ACC_STATIC |
0x0008 |
内部类是否为static |
ACC_FINAL |
0x0010 |
内部类是否为final |
ACC_INTERFACE |
0x0020 |
内部类是否为一个接口 |
ACC_ABSTRACT |
0x0400 |
内部类是否为abstract |
ACC_SYNTHETIC |
0x1000 |
内部类是否为编译器自己主动产生 |
ACC_ANNOTATION |
0x4000 |
内部类是否是一个注解 |
ACC_ENUM |
0x4000 |
内部类是否是一个枚举 |
LineNumberTale属性:用于描写叙述Java源代码的行号与字节码行号之间的相应关系,非执行时必需属性。会默认生成至Class文件里,能够使用Javac的-g:none或-g:lines关闭或要求生成该项属性信息,其结构例如以下:
类型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
line_number_table_length |
1 |
line_number_info |
line_number_table |
line_number_table_length |
line_number_table是一组line_number_info类型数据的集合。其所包括的line_number_info类型数据的数量为line_number_table_length。line_number_info结构例如以下:
类型 |
名称 |
数量 |
说明 |
u2 |
start_pc |
1 |
字节码行号 |
u2 |
line_number |
1 |
Java源代码行号 |
不生成该属性的最大影响是:1,抛出异常时,堆栈将不会显示出错的行号。2。调试程序时无法依照源代码设置断点
LocalVariableTable属性:用于描写叙述栈帧中局部变量表中的变量与Java源代码中定义的变量之间的关系。非执行时必需属性,默认不会生成至Class文件里,能够使用Javac的-g:none或-g:vars关闭或要求生成该项属性信息。其结构例如以下:
类型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
local_variable_table_length |
1 |
local_variable_info |
local_variable_table |
local_variable_table_length |
local_variable_table是一组local_variable_info类型数据的集合,其所包括的local_variable_info类型数据的数量为local_variable_table_length,local_variable_info结构例如以下:
类型 |
名称 |
数量 |
说明 |
u2 |
start_pc |
1 |
局部变量的生命周期開始的字节码偏移量 |
u2 |
length |
1 |
局部变量作用范围覆盖的长度 |
u2 |
name_index |
1 |
指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量名称 |
u2 |
descriptor_index |
1 |
指向常量池中CONSTANT_Utf8_info类型常量的索引。局部变量描写叙述符 |
u2 |
index |
1 |
局部变量在栈帧局部变量表中Slot的位置,假设这个变量的数据类型为64位类型(long或double), 它占用的Slot为index和index+1这2个位置 |
start_pc + length即为该局部变量在字节码中的作用域范围
不生成该属性的最大影响是:1,当其它人引用这种方法时,全部的參数名称都将丢失,IDE可能会使用诸如arg0、arg1之类的占位符取代原有的參数名称,对代码执行无影响,会给代码的编写带来不便。2,调试时调试器无法依据參数名称从执行上下文中获取參数值
SourceFile属性:用于记录生成这个Class文件的源代码文件名,为可选项,能够使用Javac的-g:none或-g:source关闭或要求生成该项属性信息,其结构例如以下:
型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
sourcefile_index |
1 |
能够看出SourceFile属性是一个定长属性,sourcefile_index是指向常量池中一CONSTANT_Utf8_info类型常量的索引。常量的值为源代码文件的文件名称
对大多数文件,类名和文件名称是一致的,少数特殊类除外(如:内部类)。此时假设不生成这项属性。当抛出异常时,堆栈中将不会显示出错误代码所属的文件名称
Deprecated属性和Synthetic属性:这两个属性都属于标志类型的布尔属性。仅仅存在有和没有的差别。没有属性值的概念
Deprecated属性表示某个类、字段或方法已经被程序作者定为不再推荐使用。可在代码中使用@Deprecated注解进行设置
Synthetic属性表示该字段或方法不是由Java源代码直接产生的,而是由编译器自行加入的(当然也可设置訪问标志中的ACC_SYNTHETIC标志。全部由非用户代码产生的类、方法和字段都应当至少设置Synthetic属性和ACC_SYNTHETIC标志位中的一项,唯一的例外是实例构造器<init>和类构造器<clinit>方法)
这两项属性的结构为(当然attribute_length的值必须为0x00000000):
类型 |
名称 |
数量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
起始2位为0x0001。说明有一个类属性。接下来2位为属性的名称,0x0014,指向常量池中第20个常量:SourceFile。
接下来4位为0x00000002,说明属性体长度为2字节。最后2个字节为0x0014。指向常量池中第21个常量:Test.java。即这个Class文件的源代码文件名称为Test.java
PS:
1,全限定名:将类全名中的“.”替换为“/”,为了保证多个连续的全限定名之间不产生混淆,在最后加上“;”表示全限定名结束。
比如:"com.test.Test"类的全限定名为"com/test/Test;"
2,简单名称:没有类型和參数修饰的方法或字段名称。比如:"public void add(int a,int b){...}"该方法的简单名称为"add","int a = 123;"该字段的简单名称为"a"
3。描写叙述符:描写叙述字段的数据类型、方法的參数列表(包含数量、类型和顺序)和返回值。依据描写叙述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示
标识字符 |
含义 |
B |
基本类型byte |
C |
基本类型char |
D |
基本类型double |
F |
基本类型float |
I |
基本类型int |
J |
基本类型long |
S |
基本类型short |
Z |
基本类型boolean |
V |
特殊类型void |
L |
对象类型,如:Ljava/lang/Object; |
对于数组类型,每一维将使用一个前置的“[”字符来描写叙述,如:"int[]"将被记录为"[I","String[][]"将被记录为"[[Ljava/lang/String;"
用描写叙述符描写叙述方法时,依照先參数列表,后返回值的顺序描写叙述,參数列表依照參数的严格顺序放在一组"()"之内。如:方法"String getAll(int id,String name)"的描写叙述符为"(I,Ljava/lang/String;)Ljava/lang/String;"
4,Slot。虚拟机为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型占用1个Slot,64位的数据类型(long和double)占用2个Slot