语言无关系的关键在 于
JVM
和字节码
;Java虚拟机不与任何编译成class字节码的语言绑定,只要能够编译成有效的Class字节码都解析执行。
一、Class文件结构
任何一个Class文件都对应着唯一 一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。
问题: 当出现超过8位字节码来表示的数据项怎么办呢?
会按照高位在前的方式分割成若干个8位字节进行存储;补充大端法
和小端法
二 如何描述Class文件
用
无符号
和表
组合成类似与C结构的伪结构来描述Class文件。
三、ClassFile Structure
一个class 由单个ClassFile
结构组成。ClassFile由下面结构组成。
ClassFile {
u4 magic; //识别Class文件格式,具体值为0xCAFEBABE; 即魔术
u2 minor_version;//次版本号
u2 major_version;//主版本号
u2 constant_pool_count;//常量池容量计数
cp_info constant_pool[constant_pool_count-1];
//它代表各种各样的字符串常量、类和接口名、字段名以及在ClassFile结构及其子结构中引用的其他常量。
//每个常量池表条目的格式由它的第一个“标记”字节表示。
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];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
3.1 文件结构解析
Class
文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符
特定描述 |
---|
8位字节为单位 |
顺序有严格控制 |
大小有规律,因为表是符合结构数据,大小不确定外,其他都是大小确定的。 |
类型
描述Class类文件结构的类型
类型 | 描述 |
---|---|
无符号 | 以u1 、u2 、u4 、u8 来分别代表1个字节 、2个字节 、4个字节 和8个字节 的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。 |
表 | 表是由多个无符号数 或者其他表 作为数据项构成的复合数据类型,所有表都习惯性地以_info 结尾。表用于描述有层次关系的复合结构的数据 |
3.2 字节码文件解析
将下面一点代码使用JDK1.8
编译成Class文件进行讲解
package org.fenixsoft.clazz;
public class TestClass {
private int m;
public int inc() {
return m + 1;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
通过命令javac
将这个类编译成classs文件。notepad++
装一个HEX-Editor
插件后,打开这个class文件。
接下来就是解析这份字节码。
3.2.1 魔术 cafe babe
u4 magic;
- 1
每个Class文件的头四个字节称为魔数,它的唯一作用是用来确定该文件是否为一个能被虚拟机接受的Class文件。使用魔数而不使用文件扩展名是出于安全方面的考虑,因为文件扩展名可以很随意的被改动。
3.2.2 版本号
u2 minor_version;//次版本号
u2 major_version;//主版本号
- 1
- 2
minor_version
:占2字节
,次版本号,0x0000
majro_version
:占2字节
,主版本号,0x0034
, 转换过来就是52
3.2.3 常量池(constant_pool_count 和 constant_pool)
ClassFile {
...
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
constant_pool_count
: 由于常量池中常量的数量是不固定
的,所以在常量池的入口需要放置一项u2
类型的数据,代表常量池容量计数值
(计数从1开始,其他计数从0开始,因为0有其他作用)
(一) 常量池数目:
常量池容量(偏移地址:0x00000008)为十六进制数0x0016
,即十进制的22
,这就代表常量池中有21
项常量,索引值范围为1~21
。
(二)常量池内容:
constant_pool
: 主要存放两大类常量:字面量
(Literal)和符号引用
(Symbolic References)。
在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从
常量池
获得对应的符号引用
,再在类创建时或运行时解析
、翻译
到具体的内存地址
之中
常量池的都是表结构
,如上图展示,但是在常量池中的表的结构
又是怎么样的呢?
注意:所有常量池中的条码都具有下面通用结构:
cp_info {
u1 tag;
u1 info[];
}
- 1
- 2
- 3
- 4
- 5
每个条码都有一个tag 去标志它
结合Class字节码来讲解常量池中各个类型的结构
下面圈定了常量池中的内容,通过
tag
定位到具体哪个类型
,根据类型的结构进行逐一分析
按照常量池中类型,整理出21个记录
序号 | 字节码 | 常量池类型 | 字符串 |
---|---|---|---|
1 | 0a 00 04 00 12 | CONSTANT_Methodref_info | |
2 | 09 00 03 00 13 | CONSTANT_Fieldref | |
3 | 07 00 14 | CONSTANT_Class | |
4 | 07 00 15 | CONSTANT_Class | |
5 | 01 00 01 6d | CONSTANT_Utf8_info | m |
6 | 01 00 01 49 | CONSTANT_Utf8_info | i |
7 | 01 00 06 36 69 6e 69 74 3e | CONSTANT_Utf8_info | |
8 | 01 00 03 28 29 56 | CONSTANT_Utf8_info | () |
9 | 01 00 04 43 6f 64 65 | CONSTANT_Utf8_info | Code |
10 | 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 65 | CONSTANT_Utf8_info | LineNumberTable |
11 | 01 00 12 4c 6f 63 61 6c 56 72 69 61 62 6c 65 54 61 62 6c 65 | CONSTANT_Utf8_info | LocalVariableTable |
12 | 01 00 04 74 68 69 73 | CONSTANT_Utf8_info | this |
13 | 01 00 1f 4c 6f 72 67 2f 66 65 6e 69 78 73 6f 66 74 2f 63 6c 61 7a 7a 2f 54 65 73 74 43 6c 61 73 74 3b | CONSTANT_Utf8_info | Long/fenixsoft/clazz/TestClass; |
14 | 01 00 03 69 6e | CONSTANT_Utf8_info | inc |
15 | 01 00 03 28 29 49 | CONSTANT_Utf8_info | ()I |
16 | 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 | CONSTANT_Utf8_info | SourceFile |
17 | 01 00 0e 54 65 73 74 43 6c 61 73 73 2e 6a 61 76 61 | CONSTANT_Utf8_info | TestClass.java |
18 | 0c 00 07 00 08 | CONSTANT_NameAndType | |
19 | 0c 00 05 00 06 | CONSTANT_NameAndType | |
20 | 01 00 1d 6f 72 67 2f 66 65 6e 69 78 73 6f 66 74 2e 63 6c 61 7a 7a 2f 54 65 73 74 43 6c 61 73 73 | CONSTANT_Utf8_info | org/fenixsoft/clazz/TestClass |
21 | 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 | CONSTANT_Utf8_info | java/lang/Object |
constant_pool table
把上图看做常量池表; 可以理解为将所有
同时也可利用javap
命令整理
3.2.3.1 常量池分析
(一) 根据tag值找类型:
在常量池后面的第一个地址(偏移地址:0x0000000A),是 0x0A (十进制为10),tag=10
; 找到Constant Pool tag
定位到了 CONSTANT_Methodref
这个类型。
(二) 结构类型:
Fields
, methods
, and interface methods
具有相似的结构
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
分别对 CONSTANT_Methodref_info
的 tag
, class_index
,name_and_type_index
进行介绍
items | 描述 |
---|---|
tag | CONSTANT_Methodref_info 结构的tag 值为10 |
class_index | CONSTANT_Methodref_info 结构的class_index 必须是一个类类型,而不是接口类型 |
name_and_type_index | name_and_type_index 的值必须是constant_pool表 中的一个有效索引;这索引值上的对应的常量池条目(The constant_pool entry) 也一定是CONSTANT_NameAndType_info 结构;这个条目也是具有字段或方法作为成员的类或接口类型 |
【entry 条目;词条;账目;记录 】
补充:假如
CONSTANT_Methodref_info
结构的方法名称以'<' ('\u003c')
开始,那么这个方法一定是特定的<init>
; 代表一个实例初始方法,返回类型也一定是 void。
CONSTANT_NameAndType_info
结构类型
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
- 1
- 2
- 3
- 4
- 5
tag:
CONSTANT_NameAndType_info
的tag
值一定是 12name_index : 它的值一定是
constant_pool table
(常量池表) 有效索引值。那个索引对应的constant_pool entry
(常量池条目),一定是CONSTANT_Utf8_info
结构; 代表特定的 方法 名; 也可以表示字段或方法的有效限定名descriptor_index: 它的值是
constant_pool table
(常量池表)的一个有效索引,这个constant_pool entry
将是一个CONSTANT_Utf8_info
为结构; 代表字段或者方法的描述符。
结构: CONSTANT_Utf8_info
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
- 1
- 2
- 3
- 4
- 5
这个CONSTANT_Utf8_info
结构别用来表示常量字符串值。
- tag:
CONSTANT_Utf8_info
的tag值一定是 1 - length: bytes数组的长度值;不是实际字符串的长度值。
- bytes:字节数组包含了字符串的每个字节
- 如果没有字节可是值为0
- 也可能是因为没有字节分布于 [(byte)0xf0 , (byte)0xff] (0-255)
length值说明了这个UTF-8编码的字符串长度是多少字节,它后面紧跟着的长度为length字节的连续数据是一个使用UTF-8缩略编码表示的字符串。UTF-8缩略编码与普通UTF-8编码的区别是:从
'\u0001'到'\u007f'
之间的字符(相当于1~127的ASCII码)的缩略编码使用一个字节表示,从'\u0080'到'\u07ff
‘之间的所有字符的缩略编码用两个字节表示,从'\u0800'到'\uffff'
之间的所有字符的缩略编码就按照普通UTF-8编码规则使用三个字节表示
常量池中14中常量项结构总表
可以通过查阅总表来获取各项信息
没有出现在代码中的其他常量
其中有一些常量似乎从来没有在代码中出现过,如
“I”
、“V”
、“<init>”
、“LineNumberTable”
、
“LocalVariableTable”
等,这些看起来在代码任何一处都没有出现过的常量是哪里来的呢
3.2.4 访问标记(access_flags)
这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等
TestClass
是一个普通Java类,不是接口、枚举或者注解,被public关键字修饰但没有被声明为final和abstract,并且它使用了JDK 1.2之后的编译器进行编译,因此它的ACC_PUBLIC
、ACC_SUPER
标志应当为真
,而ACC_FINAL
、ACC_INTERFACE
、ACC_ABSTRACT
、ACC_SYNTHETIC
、ACC_ANNOTATION
、CC_ENUM
这6个标志应当为假
,因此它的access_flags的值应为:0x0001|0x0020=0x0021
。
3.2.5 类索引、父类索引与接口索引集合
类索引(this_class
)和父类索引(super_class
)都是一个u2
类型的数据,而接口索引集合(interfaces
)是一组u2
类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系
- 类索引用(
this_class
)于确定这个类的全限定名, - 父类索引(
super_clas
s)用于确定这个类的父类的全限定名。 - 由于Java语言不允许多重继承,所以父类索引只有一个; 但是接口可以实现好几个,所以是个集合,
- 除了java.lang.Object之外, 所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0
结构描述:
item | desc |
---|---|
this_class | this_class 的值一定是常量池中的有效索引,这个索引对应的常量项是一个CONSTANT_Class_info ;这个表示由这个类文件定义的类或接口 |
super_class | 可能是0 ,或者是常量池中有效索引,这个索引对应的常量项是一个CONSTANT_Class_info ;代表这个类文件对应类的父类;直接超类和它的任何超类都不能在其类文件结构的access flags 项中设置ACC_FINAL 标志;加入这个值为0,那个这个文件一定是Object类 |
interfaces_count | 接口数量 |
interfaces[] | 数组中的每个值都必须是常量池中的有效索引,每个索引对应的常量项都是CONSTANT_Class_info 这样的结构。 顺序从左到右 |
因为interfaces_count
数量为 0
,所以后面的interfaces[]
就不占用地址。
3.2.6 字段表集合
结合案例,字段表的数量为
1
个;让后对这个进行分析
每个字段的结构都如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
一个类文件中
没有
两个字段可能具有相同的名称和描述。
(一) 访问标识符( access_flags )如下:
标志 值 描述 ACC_PUBLIC 0x0001 public 访问修饰符 ACC_PRIVATE 0x0002 private 访问修饰符 ACC_PROTECTED 0x0004 protected 访问修饰符 ACC_STATIC 0x0008 static ACC_FINAL 0x0010 final ACC_VOLATILE 0x0040 valatile ACC_TRANSIENT 0x0080 transient ACC_SYNTHETIC 0x1000 synthetic ACC_ENUM 0x4000 enum
标志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | public 访问修饰符 |
ACC_PRIVATE | 0x0002 | private 访问修饰符 |
ACC_PROTECTED | 0x0004 | protected 访问修饰符 |
ACC_STATIC | 0x0008 | static |
ACC_FINAL | 0x0010 | final |
ACC_VOLATILE | 0x0040 | valatile |
ACC_TRANSIENT | 0x0080 | transient |
ACC_SYNTHETIC | 0x1000 | synthetic |
ACC_ENUM | 0x4000 | enum |
对访问标识符的解释 |
---|
ACC_PUBLIC 、ACC_PRIVATE 、ACC_PROTECTED 三个标志最多只能选择其一 |
接口中的字段必须有ACC_PUBLIC 、ACC_STATIC 、ACC_FINAL 标志 |
ACC_ENUM 标志指示该字段用于保存枚举类型的元素 |
ACC_SYNTHETIC 标志表明该字段是由编译器生成的,并没有出现在源代码中 |
(二) name_index:
对常量池的引用
, 其值为常量池中的有效索引,代表着字段的简单名称。
(三) descriptor_index:
- 描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。
- 根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的
void
类型都用一个大写V
字符来表示,而对象类型则用字符L
加对象的全限定名来表示
对于数组类型,每一维度将使用一个前置的
[
字符来描述,如一个定义为java.lang.String[][]
类型的二维数组,将被记录为:[[Ljava/lang/String
,一个整型数组int[]
将被记录为[I
用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号
()
之内。
- 如方法
void inc()
的描述符为()V
, - 方法
java.lang.String toString()
的描述符为()Ljava/lang/String
, - 方法 :
int indexOf(char[]source,
int sourceOffset,
int sourceCount,
char[]target,
int targetOffset,
int targetCount,
int fromIndex)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
描述符为([CII[CIII)I
。
字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的
3.2.7 方法集合
结合案例,本次方法的数量为2
Class文件存储格式中对方法的描述
与对字段的描述
几乎采用了完全一致的方式
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
通过方法表结构,集合本文案例
(一) access_flags:
与属性进行对比:
- 因为
volatile
关键字和transient
关键字不能修饰方法,所以方法表的访问标志中没有了ACC_VOLATILE
标志和ACC_TRANSIENT
标志。 - 与之相对的,
synchronized
、native
、strictfp
和abstract
关键字可以修饰方法,所以方法表的访问标志中增加了ACC_SYNCHRONIZED
、ACC_NATIVE
、CC_STRICTFP
和ACC_ABSTRACT
标志。
(二) name_index:
(三) 描述符索引:
描述符索引
通过分析得出了public void init()
方法里的
Java代码
,经过编译器编译成字节码指令
后,存放在方法属性表集合
中一个名为Code
的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目
与字段表集合相对应的,如果父类方法在子类中没有被重写(Override
),方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的便是类构造器<clinit>
方法和实例构造器<init>
3.2.8 属性表(attribute_info)集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息;限制相对其他表相对宽松一些。
对于每个属性,它的名称需要从常量池中引用一个
CONSTANT_Utf8_info
类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。下面每个属性都具备的结构特征。
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
- 1
- 2
- 3
- 4
- 5
在分析方法表的时候已经讲述了一个Code属性
(一)Code属性描述
Java程序方法体中的代码经过
Javac
编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口
或者抽象类
中的方法就不存在Code属性
下面的图中就是Code.
Code的两个属性值:
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | attribute_name_index 是一项指向CONSTANT_Utf8_info 型常量的索引,常量值固定为Code ,它代表了该属性的属性名称 |
u4 | attribute_length | 1 | attribute_length 指示了属性值的长度 |
u2 | max_stack | 1 | max_stack 代表了操作数栈(Operand Stacks )深度的最大值。虚拟机运行的时候需要根据这个值来分配栈帧(StackFrame )中的操作栈深度。 |
u2 | max_locals | 1 | max_locals 代表了局部变量表所需的存储空间 |
u4 | code_length | 1 | code_length 代表字节码长度;理论上一个方法的字节码不超过u4,但实际是u2,如果超过这个限制,javac会拒绝 |
u1 | code | code_length | code是用于存储字节码指令的一系列字节流 |
u2 | exception_table_length | 1 | 异常表长度 |
exception_info | exception_table | exception_table_length | |
u2 | attributes_count | 1 | 属性数量 |
attribute_info | attributes | attributes_count |
Code属性表中的code(字节码)
2a b7 00 0a b1
- 1
- 读入
2a
,查表得0x2A对应的指令为aload_0
,这个指令的含义是将第0个Slot中为reference类型的本地变量推送到操作数栈顶。 - 读入
b7
,查表得0xB7
对应的指令为invokespecial
,这条指令的作用是以栈顶的reference类型
的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private方法或者它的父类的方法。这个方法有一个u2类型的参数说明具体调用哪一个方法,它指向常量池中的一个CONSTANT_Methodref_info
类型常量,即此方法的方法符号引用。 - 读入
00 01
,这是invokespecia
l的参数,查常量池得0x0001
对应的常量为实例构造器<init>
方法的符号引用。 - 读入
b1
,查表得0xB1
对应的指令为return
,含义是返回此方法,并且返回值为void
。这条指令执行后,当前方法结束。
Java虚拟机执行字节码是
基于栈的体系结构
。但是与一般基于堆栈的零字节指令又不太一样,某些指令
(如invokespecial)后面还会带有参数
继续分析Code中的剩余两个属性:LineNumberTable
和 LocalVariableTable
LineNumberTable
属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。它并不是运行时必需的属性,但默认会生成到Class文件之中,可以在Javac中分别使用-g:none
或-g:lines
选项来取消或要求生成这项信息。
如果选择不生成LineNumberTable
属性,对程序运行产生的最主要的影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点
line_number_table
是一个数量为line_number_table_length
、类型为line_number_info
的集合,line_number_info
表包括了start_pc
和line_number
两个u2
类型的数据项,前者是字节码行号,后者是Java源码行号
归纳为下面的结构
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];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
.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];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
LocalVariableTable
属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,- 它也不是运行时必需的属性,但默认会生成到Class文件之中,
- 可以在Javac中分别使用
-g:none
或-g:vars
选项来取消或要求生成这项信息。
local_variable_info项
item | desc |
---|---|
start_pc 和length | 代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围 |
name_index 和descriptor_index | 指向常量池中CONSTANT_Utf8_info 型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符 |
index | 这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类型是64位类型时(double和long),它占用的Slot为index 和index+1 两个 |
另外一个方法(不做详细介绍了):
使用javap
打印信息
args_size 为什么等于 一
<init>()
和inc()
,都没有参数的,为什么args_size会为1?
而且无论是在参数列表里还是方法体内,都没有定义任何局部变量,那Locals
又为什么会等于1?
在任何实例方法里面,都可以通过
this
关键字访问到此方法所属的对象。这个访问机制对Java程序的编写很重要,而它的实现却非常简单,仅仅是通过Javac
编译器编译的时候把对this
关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时自动传入此参数而已。因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留出第一个Slot位来存放对象实例的引用,方法参数值从1开始计算。这个处理只对实例方法有效,如果inc()
声明为static
,那Args_size
就不会等于1
而是等于0
了
ClassFile 最后一个属性:
到这个地方整个class文件就大致讲完了。
后记
- 还有很多属性没有讲解;当分析到对应的表时查询官方文档即可
code
字节码指令还没有详细讲解;将在新的一篇中详细讲解。