如同讲汇编必先讲计算机组成原理,在开始字节码之前,我们先了解一下JVM的主要构成。 在JVM的内部,主要由如下几个部分构成:
1.数据区
- 方法区:存放类定义信息、字节码、常量等数据,在Sun HotSpot JVM中,这块也称为Perm Gen。
- 堆:创建的对象信息将放入堆中,堆内部如何实现各虚拟机各不相同,对于Sun HotSpot JVM来说又分为Young Gen和Tenured Gen,更详细描述参见《[Java性能剖析]Sun JVM内存管理和垃圾回收 》
- Java栈:对于每个执行线程,会分配一个Java栈,JVM在执行过程当中,每执行一个方法,都会为方法在当前栈中增加一个栈帧,每个栈帧的信息与具体实现相关,但一般会由3部分组成:变量区,方法参数和本地变量会放入这个位置,大小是固定的,在进行方法时会先分配好,在类定义中,会由max local来指定这块区的大小;方法信息区,会包括当前类常量池的入口地址等信息,这块大小也是固定的;操作栈,与Intel体系架构中的运算使用寄存器来进行不一样,JVM的字节码的方法调用、运算等需要的参数,都是通过操作栈来传递的,在后面详细介绍中我们会进一步了解到,在类定义中,会由max stack指定最大的操作栈。关于Java栈的更详细描述参见《Java 栈内存介绍 》
- 本地方法栈:对本地方法的调用,并不会使用Java栈而是使用本地方法栈,本地方法栈的组成取决于所使用的平台和操作系统
- PC寄存器:对于每个执行线程会分配一个PC寄存器,寄存器中存放当前字节码的执行位置
2.类加载器子系统
类加载器完成类的加载工作,包括查找和装载类定义(.class)信息、连接(包括字节码验证、类变量内存分配和初始化、符号解析)和类初始化的过程
- 查找和装载类定义(.class)信息:关于这块的内容可以google到无数的描述文档,JVM内部也提供了多种查找和装载类定义的途径,譬如从本地加载类定义、从远程加载类定义,甚至需要的话,我们可以对类定义进行加密在装载的时候做处理等等,更详细的描述也可以参见《[Tomcat源码系列] Tomcat 类加载器结构 》
- 连接—字节码验证:对于加载的类定义,JVM必须确保其是合法的,包括定义的结构是合法的、声明的类信息(包括属性、方法等等)是合法的、字节码的正确性(包括确保操作码是合法的、有合法操作栈、goto语句能够到达一个合法的地址上等等)等等
- 连接—类变量内存分配和初始化:在这个阶段,类变量将分配内存,并设置一个合法的初始化值,譬如对象是null、数值型是0、布尔值是false等等
- 连接—符号解析:符号定义了类提供给外部可访问的服务以及类需要访问到的外部的服务,譬如类提供了方法给外部调用,或者类调用外部的方法,这些就是符号,符号解析过程就是将符号型的描述(譬如字符串)转换成实际的引用地址(譬如方法入口字节码指针地址),符号解析可能是在加载时进行,也有可能推迟到实际被使用到才去解析,但不管怎么样,JVM应该对外提供迟解析的印象,即不管何时解析符号,总是在符号被第一次访问的时候才会抛出异常,关于符号解析,后面会有专门的篇章更详细地介绍
- 类初始化:类的初始化是在类第一次被引用到的时候进行,类初始化包括给类属性(static)设置初始化值、调用类中的static块代码,在字节码中,会有一个专门的<clinit>方法,类初始化的时候会调用这个方法,譬如如下
- public class Test2
- {
- public static int cout = 0;
- static
- {
- for (int i=0; i<100; i++)
- {
- cout += i;
- }
- }
- }
3.执行引擎:可以理解为CPU,是JVM最核心的部分。在Java虚拟机规范中,执行引擎的行为使用指令集来定义。对于每条指令,规范都详细规定了当实现执行到该指令时应该处理什么,但却没有定义如何处理,具体策略交给JVM的具体实现
在后面的篇章,将继续介绍符号解析和字节码
参考: <<深入Java虚拟机>>
1、class文件格式:跨平台的基础
2868820670 in class file Hello
Java字节码可以跨不同的虚拟机在不同的平台上执行,这些字节码按照class文件格式的规范组成了class文件,从而为Java语言跨平台执行奠定了基石;不同的语言都可以根据class文件格式生成可以在JVM上执行的字节码,这又给Java平台带来了新的血液。
class文件是Java程序二进制的精确定义 。每一个class文件都是对一个Java 类或者接口的描述。因为有着同一的格式, 无论在何种平台上产生,也无论是在何种平台上运行,class文件的定义都能够被Java虚拟机正确地读取。下面先看看class文件的格式:
一个典型的class文件分为:MagicNumber,Version,Constant_pool,Access_flag,This_class,Super_class,Interfaces,Fields,Methods 和Attributes这十个部分,用一个数据结构可以表示如下:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中u1,u2,u4,u8分别代表1字节,2字节,4字节和8字节的无符号类型整数。
ClassFile中的字段简单说明如下:
1、MagicNumber:MagicNumber是用来标志class文件的,
虚拟机加载class文件的时候会先检查这四个字节,如果不是cafe babe则虚拟机拒绝加载该文件,这样就可以防止加载非class文件而浪费系统资源。这个字段的长度是4个字节,值是固定的cafebabe。
2、Version:version字段有2个长度都为2字节的字段组成,分别是Major Version和Minor Version,分别代表当前class文件的主版本号和次版本号。随着Java技术的不断发展,Java class文件格式会增加一些新的内容来支持Java语言的新特性。同时,不同的虚拟机支持的Java class文件的版本范围是不同的,所以在加载class文件之前可以先看看该class文件是否在当前虚拟机的支持范围之内,避免加载不支持的class文件。
3、常量池
首先是2个字节的长度字段constant_pool_count,表明常量池包含了多少个常量。
后面跟着就是constant_pool_count个常量,常量池里放的是字面常量和符号引用。
字面常量主要包含文本串以及被声明为final的常量等;符号引用包含类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符,因为java语言在编译的时候没有连接这一步,所有的引用都是运行时动态加载的,所以就需要把这些引用的信息保存在class文件里。
这里的常量又根据具体的类型分成
字符串,整形,长整型,浮点型,双精度浮点型这几种基本类型。
而符号引用保存的是对应的引用的全局限定名,所以保存的是字符串。
4、access_flag 保存了当前类的访问权限
5、this_cass 保存了当前类的全局限定名在常量池里的索引
6、super class 保存了当前类的父类的全局限定名在常量池里的索引
7、interfaces 保存了当前类实现的接口列表,包含两部分内容:interfaces_count 和interfaces[interfaces_count]
interfaces_count 指的是当前类实现的接口数目
interfaces[] 是包含interfaces_count个接口的全局限定名的索引的数组
8、
fields 保存了当前类的成员列表,包含两部分的内容:fields_count 和 fields[fields_count]
fields_count是类变量和实例变量的字段的数量总和。
fileds[]是包含字段详细信息的列表。
9、
methods 保存了当前类的方法列表,包含两部分的内容:methods_count和methods[methods_count]
methods_count是该类或者接口显示定义的方法的数量。
method[]是包含方法信息的一个详细列表。
10、
attributes 包含了当前类的attributes列表,包含两部分内容:attributes_count 和 attributes[attributes_count]
class文件的最后一部分是属性,它描述了该类或者接口所定义的一些属性信息。attributes_count指的是attributes列表中包含的attribute_info的数量。
属性可以出现在class文件的很多地方,而不只是出现在attributes列表里。如果是attributes表里的属性,那么它就是对整个class文件所对应的类或者接口的描述;如果出现在fileds的某一项里,那么它就是对该字段额外信息的描述;如果出现在methods的某一项里,那么它就是对该方法额外信息的描述。
二、手动分析class文件
上面大致讲解了一下class文件的结构,这里,我们拿一个class文件做一个简单的分析,来验证上面的说法。
先看看一个简单的类
点击(此处)折叠或打开
- public class Hello{
- private int test;
- public int test(){
- return test;
- }
- }
编译之后的class文件十六进制结果如下所示,可以用UE等十六进制编辑器打开:
点击(此处)折叠或打开
- ca fe ba be 00 00 00 32 00 12 0a 00 04 00 0e 09
- 00 03 00 0f 07 00 10 07 00 11 01 00 04 74 65 73
- 74 01 00 01 49 01 00 06 3c 69 6e 69 74 3e 01 00
- 03 28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69
- 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 03
- 28 29 49 01 00 0a 53 6f 75 72 63 65 46 69 6c 65
- 01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 0c 00 07
- 00 08 0c 00 05 00 06 01 00 05 48 65 6c 6c 6f 01
- 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65
- 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05
- 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09
- 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01
- b1 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00
- 01 00 01 00 05 00 0b 00 01 00 09 00 00 00 1d 00
- 01 00 01 00 00 00 05 2a b4 00 02 ac 00 00 00 01
- 00 0a 00 00 00 06 00 01 00 00 00 03 00 01 00 0c
- 00 00 00 02 00 0d
接下来我们就按照class文件的格式来分析上面的一串数字,还是按照之前的顺序来
1、魔数:cafebabe,这个是用来标识java class文件的,如果文件的开始不是cafebase的话,虚拟机会拒绝加载该文件。关于这个我们可以试验一下,用16进制编辑器修改cafebabe。
运行 java Hello
会抛出ClassFormatError
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value2868820670 in class file Hello
2、接下来就是version字段:00 00 00 32,前两个字节00是minor_version,后两个字节0032是major_version字段,对应的十进制值为50,也就是说当前class文件的主版本号为50,次版本号为0。前面讲过,如果version字段不在当前虚拟机支持的范围之内,虚拟机会拒绝加载。我们可以试一下,把version字段改大,例如改成00 01 00 40(主版本号为64,次版本号为1)。加载该class文件,会抛出java.lang.UnsupportedClassVersionError: Hello : Unsupported major.minor version 64.1,表示当前虚拟机不支持64.1版本的class文件。
3、接下来就是constant_pool
前面讲过,常量池里存放的是字面常量和符号引用。
1、字面常量
字面常量分别包含String,Integer,Float,Long,Double这几个类型,这些字段都是以二进制的方式存储的,所以存储的时候,只要指定类型、长度和对应的值即可。这样的话,那这些基本的常量就可以采用如下的结构来表示:
点击(此处)折叠或打开
- CONSTANT_XXX{
- type;//该常量的类型
- length;//该常量的长度,以字节为单位
- byte[];//该常量的二进制表示,包含length个字节
- }
不过对于Integer、Float、Long和Double这些常量的长度是固定不变的,所以可以省去以节省空间;但是字符串常量的长度是不能省的。
2、符号引用
符号引用包含三种特殊的字符串,它们分别是:
1、全局限定名 当一个常量表示类或者接口的时候,需要指定类的全局限定名,在class文件中,全局限定名的点用斜杠来代替,例如java.lang.Object的全局限定名为java/lang/Object
2、简单名称 类的字段名或者方法名以简单名称的方式存在常量池里,例如上面Hello.test字段在class文件里会存有形如“test”的简单名称。
3、描述符 除了成员变量的名称和方法,class文件里还要存储对应的描述符。描述符在class文件里以一种简化的方式表示,这样就可以减少class文件的大小。
成员变量的描述符和方法的修饰符可以使用下面的语法来表示:
点击(此处)折叠或打开
- FieldDescriptor:
- FieldType
-
- ComponentType:
- FieldType
-
- FieldType:
- BaseType
- ObjectType
- ArrayType
-
- BaseType:
- B
- C
- D
- F
- I
- J
- S
- Z
-
- ObjectType:
- L Classname ;
-
- ArrayType:
- [ComponentType
-
- MethodDescriptor:
- ( ParameterDescriptor* ) ReturnDescriptor
-
- ParameterDescriptor:
- FieldType
-
- ReturnDescriptor:
- FieldType
- VoidDescriptor
-
- VoidDescriptor:
- V
简单来说,对于成员变量而言,它的描述符是它的类型,类型又分为:BasicType,ObjectType和ArrayType。
- BasicType指的是byte,char,double,float,int,long,short,boolean这几种基本类型,它们分别用B、C、D、F、I、L、S、Z来表示。
- ObjectType指的是对象类型,也就是说该成员变量的类型是一个Java类,那么这种类型就用L加上Classname表示。例如一个类型为java.lang.String的变量,它的描述符为Ljava/lang/String。
- ArrayType指的是列表类型,也就是说这个成员变量是个数组。对于数组类型,每一个维度用一个前置的“[”来描述。例如一个定义为"java.lang.String[][]"的变量的描述符为[[java/lang/String;对于一个定义为“int[]”的变量,它的描述符表示为“[I”。
对于方法而言,它的描述符是先参数列表,后返回值的方式组成。参数列表按照声明的顺序排列,每个参数的格式和成员变量的格式一样。所有的参数放在一个括号里面和返回值进行分隔。返回值的格式也和成员变量的格式一样,不过返回值多了一个void类型,用“V”来表示。
例如,对于void test()方法,它的描述符表示为“()V”,而对于java.lang.String test(int[],int index)方法,它的描述符表示为“([II)Ljava/lang/String”。
这些特殊的字符串在常量池里是采取引用的方式来表示的,它们有如下的结构:
点击(此处)折叠或打开
- CONSTANT_XXX{
- type;//字符串的类型
- index;//对应的值的引用
- }
3、字符串的存储方式
在class文件里,字符串也是采用引用的方式进行存储的,它的引用指向该字符串对应的UTF-8的一个变体的表示。
点击(此处)折叠或打开
- CONSTANT_XXX{
- type;//字符串的类型
- index;//对应的值的引用
- }
对应的UTF-8表示的结构
点击(此处)折叠或打开
- CONSTANT_UTF8{
- type;
- length;
- value[];
- }
4、常量的基本格式
由上面的分析可以知道,常量的结构都是以常量的类型开始,然后才是具体的数据。这样的话,常量池里的变量都是如下的结构:
点击(此处)折叠或打开
- cp_info {
- u1 tag;
- u1 info[];
- }
其中的tag就是常量对应的类型,主要有以下几个值:
后面的
info[]字段,代表具体结构的数据,需要按照对应的结构来进行分析。
4、分析Hello.java的常量池
4.1、constant_pool_count
接下来的两个字节00 12代表常量池里包含的常量数目,也就是说这个常量池包含17个(0x0012 -1 )常量。
4.2、constant_pool 就下来就是分析这17个常量了
1)第一个变量 0a 00 04 00 0e
首先,紧接着constant_pool_count的第一个字节0a(tag=10)表示这是一个CONSTANT_Methodref。CONSTANT_Methodref的结构如下:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
其中class_index表示该方法所属的类在常量池里的索引,name_and_type_index表示该方法的名称和类型的索引。常量池里的变量的索引从1开始。
那么这个methodref结构的数据如下:
0a //tag 表示这是一个CONSTANT_Methodref_info结构
00 04 //指向第4个常量所表示的类
00 0e //指向第14个常量所表示的方法
2)第二个变量
接着是第二个常量,它的tag是09,表示这是一个CONSTANT_Fieldref的结构,它的结构如下:
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
和上面的变量基本一致。
09 //tag
00 03//指向第三个常量所表示的类
00 0f //指向第15个常量所表示的变量
3)第三个变量 07 00 10
tag为07表示是一个CONSTANT_Class变量,这个变量的结构如下:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
除了tag字段以外,还有一个name_index是指向该变量名称的一个索引。
4)第四个变量也是一个CONSTANT_Class
5)第五个变量 01 00 04 74 65 73 74
tag为1,表示这是一个CONSTANT_Utf8结构,这种结构用UTF-8的一种变体来表示字符串,结构如下所示:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
其中length表示该字符串的字节数,bytes字段包含该字符串的二进制表示。
接着tag的两个字节0004表示这个字符串的长度是4字节,也即是后面的74657374,表示的是字符串“test” 。
6)接下来的8个变量都是字符串,这里就不具体分析了。
7)第十四个常量 0c 00 07 00 08
tag为0c,表示这是一个CONSTANT_NameAndType结构,这个结构用来描述一个方法或者成员变量。具体结构如下:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
name_index表示的是该变量或者方法的名称,这里的值是0007,表示指向第7个常量,即是“<init>”。descriptor_index指向该方法的描述符的引用,这里的值是0008,表示指向第8个常量,即是“()V”,由前面描述符的语法可知,这个方法是一个无参的,返回值为void的方法。综合两个字段,可以推出这个方法是“void <init>()”。也即是指向这个NameAndType结构的Methodref的方法名为“void <init>()”,也就是说第一个常量表示的是“void <init>()”方法。
8)第十五个常量也是一个CONSTANT_NameAndType,表示的方法名为“int test()”,第2个常量引用了这个NameAndType,所以第二个常量表示的是“int test()”方法。
9)第16和17个常量也是字符串,可以按照前面的方法分析。
4.3、完整的常量池
最后分析完的常量池如下:
00 12 常量池的数目 18-1=17
0a 00 04 00 0e 方法:java.lang.Ojbect void <init>()
09 00 03 00 0f 方法 :Hello int test()
07 00 10 字符串:Hello
07 00 11 字符串:java.lang.Ojbect
01 00 04 74 65 73 74 字符串:test
01 00 01 49 字符串:I
01 00 06 3c 69 6e 69 74 3e 字符串:<init>
01 00 03 28 29 56 字符串:()V
01 00 04 43 6f 64 65 字符串:Code
01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 字符串:LineNumberTable
01 00 03 28 29 49 字符串:()I
01 00 0a 53 6f 75 72 63 65 46 69 6c 65 字符串:SourceFile
01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 字符串:Hello.java
0c 00 07 00 08 NameAndType:<init> ()V
0c 00 05 00 06 NameAndType:test I
01 00 05 48 65 6c 6c 6f 字符串:Hello
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 字符串: java/lang/Object
5、access_flag 00 21
这两个字节的数据表示这个变量的访问标志位,具体的取值见下表:
0x0021=0x0001 | 0x0020 ,也就是表示当前class的access_flag是ACC_PUBLIC|ACC_SUPER。ACC_PUBLIC和代码里的public 关键字相对应。ACC_SUPER表示当用invokespecial指令来调用父类的方法时需要特殊处理。
6、this_class 00 03
this_class存的是当前类的名称在常量池里的索引,这里指向第三个常量,即是“Hello”。
7、super_class 00 04
super_class存的是父类的名称在常量池里的索引,这里指向第四个常量,即是“java/lang/Object”。
8、interfaces
interfaces包含interfaces_count和interfaces[]两个字段。这里interfaces_count为0(0000),所以后面的内容也对应为空。
9、fields
00 01 fields count//表示有一个成员变量
00 02 00 05 00 06 00 00//成员变量的结构
每个成员变量对应一个field_info结构:
field_info {
u2 access_flags; 0002
u2 name_index; 0005
u2 descriptor_index; 0006
u2 attributes_count; 0000
attribute_info attributes[attributes_count];
}
access_flags为0002,即是ACC_PRIVATE
name_index指向常量池的第五个常量,为“test”
descriptor_index指向常量池的第6个常量为“I”
三个字段结合起来,说明这个变量是"private int test"。
接下来的是attribute字段,用来描述该变量的属性,有无这个变量没有附加属性,所以attributes_count为0,attribute_info为空。
10、methods
首先是2个字节的method_count,接下来的内容是两个method_info结构:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
前三个字段和field_info一样,可以分析出第一个方法是“public void <init>()”
00 01 ACC_PUBLIC
00 07 <init>
00 08 V()
接下来是attribute字段,也即是这个方法的附加属性,这里的attributes_count =1,也即是有一个属性。
每个属性的都是一个attribute_info结构,如下所示:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
不同的attribute通过attribute_name_index来区分。JVM规范里对以下attribute进行了预定义:
这里的
attribute_name_index值为0009,表示指向第9个常量,即是Code。Code Attribute的作用是保存该方法的结构如所对应的字节码,具体的结构如下所示:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
attribute_length表示attribute所包含的字节数,这里为0000001d,即是39个字节,不包含attribute_name_index和attribute_length字段。
max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,这里是0001
max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,这里是0001.
接下来的code_length表示该方法的所包含的字节码的字节数以及具体的指令码。
这里的字节码长度为00000005,即是后面的5个字节 2a b7 00 01 b1为对应的字节码指令的指令码。
参照下表可以将上面的指令码翻译成对应的助记符:
2a aload_0
b7 invokespecial
00 nop
01 aconst_null
b1 return
这即是该方法被调用时,虚拟机所执行的字节码
接下来是exception_table,这里存放的是处理异常的信息。每个exception_table表项由start_pc,end_pc,handler_pc,catch_type组成。
start_pc和end_pc表示在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理;handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常,这个可以用来实现finally的功能。
不过,这段代码里没有异常处理exception_table_length为0000,所以我们不做分析。
接下来是该方法的附加属性,attributes_count为0001,表示有一个附加属性。
attribute_name_index为000a,指向第十个常量,为LineNumberTable。这个属性用来表示code数组中的字节码和java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。LineNumberTable的结构如下:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
前面两个字段分别表示这个attribute的名称是LineNumberTable以及长度为00000006。接下来的0001表示line_number_table_length,表示line_number_table有一个表项,其中start_pc为 00 00,line_number为 00 00,表示第0行代码从code的第0个指令码开始。
后面的内容是第二个方法,具体就不再分析了。
11、attributes
最后剩下的内容是attributes,这里的attributes表示整个class文件的附加属性,不过结构还是和前面的attribute保持一致。
00 01表示有一个attribute。
attribute_name_index为000c,指向第12个常量,为SourceFile,说明这个属性是Source Attribute。结构如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
attribute_length为00000002
sourcefile_index为000d,表示指向常量池里的第13个常量,为Hello.java。
这个属性表明当前的class文件是从Hello.java文件编译而来。
12、最后分析完的class文件如下所示:
ca fe ba be 魔数,cafe babe
00 00 次版本号 00
00 32 主版本号 50
00 12 常量池的数目 18-1=17
0a 00 04 00 0e java.lang.Ojbect void <init>()
09 00 03 00 0f Hello int
07 00 10 Hello
07 00 11 java.lang.Ojbect
01 00 04 74 65 73 74 test
01 00 01 49 I
01 00 06 3c 69 6e 69 74 3e <init>
01 00 03 28 29 56 ()V
01 00 04 43 6f 64 65 Code
01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 LineNumberTable
01 00 03 28 29 49 ()I
01 00 0a 53 6f 75 72 63 65 46 69 6c 65 SourceFile
01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 Hello.java
0c 00 07 00 08 <init> ()V
0c 00 05 00 06 test I
01 00 05 48 65 6c 6c 6f Hello
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 java/lang/Object
00 21 access_flag
00 03 this_class
00 04 super_class
00 00 interfaces_count
00 01 fields count
00 02 00 05 00 06 00 00
00 02 method count
00 01 00 07 00 08 00 01 <init>()
00 09 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00
00 01 00 0a 00 00 00 06 00 01 00 00 00
01
01
00 01 00 05 00 0b 00 01 int test()
00 09 00 00 00 1d 00
01 00 01 00 00 00 05 2a b4 00 02 ac 00 00 00 01
00 0a 00 00 00 06 00 01 00 00 00 03
01 00 01 00 00 00 05 2a b4 00 02 ac 00 00 00 01
00 0a 00 00 00 06 00 01 00 00 00 03
00 01 00 0c SourceFile
00 00 00 02
00 00 00 02
00 0d Hello.java