[JVM]了断局: Class文件结构梳理
预警: 这是一篇枯燥的文章,然而还是得懂. 慢慢看,头疼了就出去溜溜.....
Table of Contents
一.概念
二. 示例代码
三.魔数
四.版本号
五. 常量池
六.访问标志
七.类索引、 父类索引与接口索引集合
八. 字段表集合
九.方法集合表
十.属性表集合
Code属性
Exceptions属性
LineNumberTable属性
LocalVariableTable及LocalVariableTypeTable属性
ConstantValue属性
Class文件格式采用一种类似于C语言结构体的伪结构来存储数据, 这种伪结构中只有两种数据类型: “无符号数”和“表”。
------《Java虚拟机规范》
一.概念
Class文件是一组以8个字节为基础单位的二进制流, 各个数据项目严格按照顺序紧凑地排列在文件之中, 中间没有添加任何分隔符, 这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据, 没有空隙存在。 当遇到需要占用8个字节以上空间的数据项
时, 则会按照高位在前[2]的方式分割成若干个8个字节进行存储。
任何一个Class文件都对应着唯一的一个类或接口的定义信息,但是反过来说, 类或接口并不一定都得定义在文件里(譬如类或接口也可以动态生成, 直接送入类加载器中) 。
Class文件由两部分数据类型组成: “无符号数”和“表”
※无符号数属于基本的数据类型, 以u1、 u2、 u4、 u8来分别代表1个字节、 2个字节、 4个字节和8个字节的无符号数, 无符号数可以用来描述数字、 索引引用、 数量值或者按照UTF-8编码构成字符串值。
※表是由多个无符号数或者其他表作为数据项构成的复合数据类型, 为了便于区分,所有表的命名都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据, 整个Class文件本质上也可以视作是一张表, 这张表由下表所示的数据项按严格顺序排列构成.Class的结构不像XML等描述语言, 由于它没有任何分隔符号所以在下表中的数据项, 无论是顺序还是数量, 甚至于数据存储的字节序(Byte Ordering, Class文件中字节序为Big-Endian) 这样的细节, 都是被严格限定的, 哪个字节代表什么含义, 长度是多少, 先后顺序如何, 全部都不允许改变。
序号 | 类型 | 名称 | 含义 | 数量 |
---|---|---|---|---|
1. | u4 | magic |
魔术: 0xCAFEBABE 标识是否能被虚拟机接受的Class文件 |
1 |
2. | u2 | minor_version |
次版本 jdk1.2到jdk12 固定为零 |
1 |
3. | u2 | major_version |
主版本 从45开始, JDK8版本为 52 |
1 |
4. | u2 | constant_pool_count | 常量池大小 | 1 |
5. | cp_info | constant_pool | 常量池内容 | constant_pool_count-1 |
6. | u2 | access_flags | 描述的是当前类(或者接口)的访问修饰符, 如public, private等, 此外, 这里面还存在一个标志位, 标志当前的额这个class描述的是类, 还是接口 | 1 |
7. | u2 | this_class | 类索引 | 1 |
8. | u2 | super_class | 父类索引 | 1 |
9. | u2 | interfaces_count | 接口大小 | 1 |
10. | u2 | interfaces | 接口集合 | interfaces_count |
11. | u2 | fields_count | 1 | |
12. | field_info | fields | fields_count | |
13. | u2 | methods_count | 1 | |
14. | method_info | methods | methods_count | |
15. | u2 | attributes_count | 1 | |
16. | attribute_info | attributes | attributes_count |
二. 示例代码
这里是要演示的代码
package com.jvm;public class JVM {public static final String ID = "正正正正正正正正正正" ;public void hello( ){String data = "hello : " +ID ;System.out.println(data);}public static int sum(int a ,int b ){int c = a + b ;return c;}public static void main(String[] args) {int a = 1 ;int b = 2 ;int x = sum(a, b) ;System.out.println("x: "+x);JVM jvm = new JVM();jvm.hello();}
}
转换为二进制后:
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: CA FE BA BE 00 00 00 34 00 46 0A 00 0F 00 2B 07 J~:>...4.F....+.
00000010: 00 2C 08 00 2D 09 00 2E 00 2F 0A 00 30 00 31 0A .,..-..../..0.1.
00000020: 00 02 00 32 07 00 33 0A 00 07 00 2B 08 00 34 0A ...2..3....+..4.
00000030: 00 07 00 35 0A 00 07 00 36 0A 00 07 00 37 0A 00 ...5....6....7..
00000040: 02 00 2B 0A 00 02 00 38 07 00 39 01 00 02 49 44 ..+....8..9...ID
00000050: 01 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 ...Ljava/lang/St
00000060: 72 69 6E 67 3B 01 00 0D 43 6F 6E 73 74 61 6E 74 ring;...Constant
00000070: 56 61 6C 75 65 08 00 3A 01 00 06 3C 69 6E 69 74 Value..:...<init
00000080: 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 >...()V...Code..
00000090: 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 .LineNumberTable
000000a0: 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 ...LocalVariable
000000b0: 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 0D 4C Table...this...L
000000c0: 63 6F 6D 2F 6A 76 6D 2F 4A 56 4D 3B 01 00 05 68 com/jvm/JVM;...h
000000d0: 65 6C 6C 6F 01 00 04 64 61 74 61 01 00 03 73 75 ello...data...su
000000e0: 6D 01 00 05 28 49 49 29 49 01 00 01 61 01 00 01 m...(II)I...a...
000000f0: 49 01 00 01 62 01 00 01 63 01 00 04 6D 61 69 6E I...b...c...main
00000100: 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F ...([Ljava/lang/
00000110: 53 74 72 69 6E 67 3B 29 56 01 00 04 61 72 67 73 String;)V...args
00000120: 01 00 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 ...[Ljava/lang/S
00000130: 74 72 69 6E 67 3B 01 00 01 78 01 00 03 6A 76 6D tring;...x...jvm
00000140: 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 08 ...SourceFile...
00000150: 4A 56 4D 2E 6A 61 76 61 0C 00 14 00 15 01 00 0B JVM.java........
00000160: 63 6F 6D 2F 6A 76 6D 2F 4A 56 4D 01 00 26 68 65 com/jvm/JVM..&he
00000170: 6C 6C 6F 20 3A 20 E6 AD A3 E6 AD A3 E6 AD A3 E6 llo.:.f-#f-#f-#f
00000180: AD A3 E6 AD A3 E6 AD A3 E6 AD A3 E6 AD A3 E6 AD -#f-#f-#f-#f-#f-
00000190: A3 E6 AD A3 07 00 3B 0C 00 3C 00 3D 07 00 3E 0C #f-#..;..<.=..>.
000001a0: 00 3F 00 40 0C 00 1D 00 1E 01 00 17 6A 61 76 61 .?.@........java
000001b0: 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 42 75 69 6C /lang/StringBuil
000001c0: 64 65 72 01 00 03 78 3A 20 0C 00 41 00 42 0C 00 der...x:...A.B..
000001d0: 41 00 43 0C 00 44 00 45 0C 00 1B 00 15 01 00 10 A.C..D.E........
000001e0: 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 java/lang/Object
000001f0: 01 00 1E E6 AD A3 E6 AD A3 E6 AD A3 E6 AD A3 E6 ...f-#f-#f-#f-#f
00000200: AD A3 E6 AD A3 E6 AD A3 E6 AD A3 E6 AD A3 E6 AD -#f-#f-#f-#f-#f-
00000210: A3 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 #...java/lang/Sy
00000220: 73 74 65 6D 01 00 03 6F 75 74 01 00 15 4C 6A 61 stem...out...Lja
00000230: 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 va/io/PrintStrea
00000240: 6D 3B 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 m;...java/io/Pri
00000250: 6E 74 53 74 72 65 61 6D 01 00 07 70 72 69 6E 74 ntStream...print
00000260: 6C 6E 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 ln...(Ljava/lang
00000270: 2F 53 74 72 69 6E 67 3B 29 56 01 00 06 61 70 70 /String;)V...app
00000280: 65 6E 64 01 00 2D 28 4C 6A 61 76 61 2F 6C 61 6E end..-(Ljava/lan
00000290: 67 2F 53 74 72 69 6E 67 3B 29 4C 6A 61 76 61 2F g/String;)Ljava/
000002a0: 6C 61 6E 67 2F 53 74 72 69 6E 67 42 75 69 6C 64 lang/StringBuild
000002b0: 65 72 3B 01 00 1C 28 49 29 4C 6A 61 76 61 2F 6C er;...(I)Ljava/l
000002c0: 61 6E 67 2F 53 74 72 69 6E 67 42 75 69 6C 64 65 ang/StringBuilde
000002d0: 72 3B 01 00 08 74 6F 53 74 72 69 6E 67 01 00 14 r;...toString...
000002e0: 28 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 ()Ljava/lang/Str
000002f0: 69 6E 67 3B 00 21 00 02 00 0F 00 00 00 01 00 19 ing;.!..........
00000300: 00 10 00 11 00 01 00 12 00 00 00 02 00 13 00 04 ................
00000310: 00 01 00 14 00 15 00 01 00 16 00 00 00 2F 00 01 ............./..
00000320: 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 02 00 ......*7..1.....
00000330: 17 00 00 00 06 00 01 00 00 00 03 00 18 00 00 00 ................
00000340: 0C 00 01 00 00 00 05 00 19 00 1A 00 00 00 01 00 ................
00000350: 1B 00 15 00 01 00 16 00 00 00 47 00 02 00 02 00 ..........G.....
00000360: 00 00 0B 12 03 4C B2 00 04 2B B6 00 05 B1 00 00 .....L2..+6..1..
00000370: 00 02 00 17 00 00 00 0E 00 03 00 00 00 08 00 03 ................
00000380: 00 09 00 0A 00 0A 00 18 00 00 00 16 00 02 00 00 ................
00000390: 00 0B 00 19 00 1A 00 00 00 03 00 08 00 1C 00 11 ................
000003a0: 00 01 00 09 00 1D 00 1E 00 01 00 16 00 00 00 48 ...............H
000003b0: 00 02 00 03 00 00 00 06 1A 1B 60 3D 1C AC 00 00 ..........`=.,..
000003c0: 00 02 00 17 00 00 00 0A 00 02 00 00 00 0D 00 04 ................
000003d0: 00 0E 00 18 00 00 00 20 00 03 00 00 00 06 00 1F ................
000003e0: 00 20 00 00 00 00 00 06 00 21 00 20 00 01 00 04 .........!......
000003f0: 00 02 00 22 00 20 00 02 00 09 00 23 00 24 00 01 ...".......#.$..
00000400: 00 16 00 00 00 9C 00 03 00 05 00 00 00 32 04 3C .............2.<
00000410: 05 3D 1B 1C B8 00 06 3E B2 00 04 BB 00 07 59 B7 .=..8..>2..;..Y7
00000420: 00 08 12 09 B6 00 0A 1D B6 00 0B B6 00 0C B6 00 ....6...6..6..6.
00000430: 05 BB 00 02 59 B7 00 0D 3A 04 19 04 B6 00 0E B1 .;..Y7..:...6..1
00000440: 00 00 00 02 00 17 00 00 00 1E 00 07 00 00 00 12 ................
00000450: 00 02 00 13 00 04 00 14 00 0A 00 15 00 23 00 17 .............#..
00000460: 00 2C 00 18 00 31 00 19 00 18 00 00 00 34 00 05 .,...1.......4..
00000470: 00 00 00 32 00 25 00 26 00 00 00 02 00 30 00 1F ...2.%.&.....0..
00000480: 00 20 00 01 00 04 00 2E 00 21 00 20 00 02 00 0A .........!......
00000490: 00 28 00 27 00 20 00 03 00 2C 00 06 00 28 00 1A .(.'.....,...(..
000004a0: 00 04 00 01 00 29 00 00 00 02 00 2A .....).....*
三.魔数
每个Class文件的头4个字节被称为魔数(Magic Number) , 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。 如下图:
四.版本号
第5和第6个字节是次版本号(Minor Version) , 第7和第8个字节是主版本号(Major Version)
JDK8 , 主版本号为: 52 ,次版本号固定为 0 [ 次版本号: JDK1.2到JDK12固定为零, JDK12之后一些特殊版本会用到 ] .
Java的版本号是从45开始的, JDK 1.1之后的每个JDK大版本发布主版本号向上加1(JDK 1.0~1.1使用了45.0~45.3的版本号) , 高版本的JDK能向下兼容以前版本的Class文件, 但不能运行以后版本的Class文件.
《Java虚拟机规范》在Class文件校验部分明确要求:
即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。
五. 常量池
常量池可以比喻为Class文件里的资源仓库, 它是Class文件结构中与其他项目关联最多的数据, 通常也是占用Class文件空间最大的数据项目之一, 另外, 它还是在Class文件中第一个出现的表类型数据项目.
常量池中常量的数量是不固定的,在常量池的入口有一项u2类型的数据, 代表常量池容量计数值(constant_pool_count) 。这个容量计数是从1而不是0开始的, 如下图所示: 常量池容量(偏移地址: 0x00000008) 为十六进制数0x0046, 即十进制的70, 这就代表常量池中有69项常量, 索引值范围为1~69。
在Class文件格式规范制定之时, 设计者将第0项常量空出来是有特殊考虑的, 这样做的目的在于, 如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义, 可以把索引值设置为0来表示。
Class文件结构中只有常量池的容量计数是从1开始, 对于其他集合类型, 包括接口索引集合、 字段表集合、 方法表集合等的容量计数都与一般习惯相同, 是从0开始。
常量池中主要存放两大类常量: 字面量(Literal) 和符号引用(SymbolicReferences) 。
字面量比较接近于Java语言层面的常量概念, 如文本字符串、 被声明为final的常量值等。
而符号引用则属于编译原理方面的概念.
主要包括下面几类常量:
·被模块导出或者开放的包(Package)
·类和接口的全限定名(Fully Qualified Name)
·字段的名称和描述符(Descriptor)
·方法的名称和描述符
·方法句柄和方法类型(Method Handle、 Method Type、 Invoke Dynamic)
·动态调用点和动态常量(Dynamically-Computed Call Site、 Dynamically-Computed
Constant)
Java代码在进行Javac编译的时候,是在虚拟机加载Class文件的时候进行动态连接.在Class文件中不会保存各个方法、 字段最终在内存中的布局信息, 这些字段、 方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址, 也就无法直接被虚拟机使用的。 当虚拟机做类加载时, 将会从常量池获得对应的符号引用, 再在类创建时或运行时解析、 翻译到具体的内存地址之中。
常量池中每一项常量都是一个表, 最初常量表中共有11种结构各不相同的表结构数据, 后来为了更好地支持动态语言调用, 额外增加了4种动态语言相关的常量[1], 为了支持Java模块化系统(Jigsaw), 又加入了CONSTANT_Module_info和CONSTANT_Package_info两个常量, 所以截至JDK 13, 常量表中分别有17种不同类型的常量。
这17类表都有一个共同的特点, 表结构起始的第一位是个u1类型的标志位,代表着当前常量属于哪种常量类型。
常量池是最烦琐的数据, 是因为这17种常量类型各自有着完全独立的数据结构, 两两之间并没有什么共性和联系.
常量池清单:
序号 | 类型 | 标志 | 16进制码 | 描述 |
---|---|---|---|---|
1. | CONSTANT_Utf8_info | 1 | 01 | UTF-8编码的字符串 |
2. | CONSTANT_Interger_info | 3 | 03 | 整型字面量 |
3. | CONSTANT_Float_info | 4 | 04 | 浮点型字面量 |
4. | CONSTANT_Long_info | 5 | 05 | 长整形字面量 |
5. | CONSTANT_Double_info | 6 | 06 | 双精度浮点型字面量 |
6. | CONSTANT_Class_info | 7 | 07 | 类或接口的符号引用 |
7. | CONSTANT_String_info | 8 | 08 | 字符串类型字面量 |
8. | CONSTANT_Fieldref_info | 9 | 09 | 字段的符号引用 |
9. | CONSTANT_Methodref_info | 10 | 0A | 类中方法的符号引用 |
10. | CONSTANT_InterfaceMethodref_info | 11 | 0B | 接口方法的符号引用 |
11. | CONSTANT_NameAndType_info | 12 | 0C | 字段或方法的部分符号引用 |
12. | CONSTANT_MethodHandle_info | 15 | 0F | 表示方法句柄 |
13. | CONSTANT_MethodType_info | 16 | 10 | 表示方法类型 |
14. | CONSTANT_Dynamic_info | 17 | 11 | 表示一个动态计算常量 |
15. | CONSTANT_InvokeDynamic_info | 18 | 12 | 表示一个动态方法调用点 |
16. | CONSTANT_Module_info | 19 | 13 | 表示一个模块 |
17. | CONSTANT_Package_info | 20 | 14 | 表示一个模块中开放或者导出的包 |
常量池中的17种数据类型的结构总表
常量 | 含义 | 项目 | 类型 | 描述 |
---|---|---|---|---|
CONSTANT_Utf8_info | UTF-8编码的字符串 | tag | u1 | 值为1 |
length | u2 | UTF-8编码的字符串占用了字节数 | ||
bytes | u1 | 长度为length的UTF-8编码的字符串 | ||
CONSTANT_Interger_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的索引项 | ||
CONSTANT_Methodref_info | 类中方法的符号引用 | tag | u1 | 值为10 |
index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | ||
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | ||
CONSTANT_InterfaceMethodref_info | 接口方法的符号引用 | tag | u1 | 值为11 |
index | u2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引项 | ||
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | ||
CONSTANT_NameAndType_info | 字段或方法的部分符号引用 | tag | u1 | 值为12 |
index | u2 | 指向该字段或方法名称常量项的索引 | ||
index |
u2 |
指向该字段或方法描述符常量项的索引 | ||
CONSTANT_MethodHandle_info | 表示方法句柄 | tag | u1 | 值为15 |
reference_kind | u1 |
值必须在1至9之间(包括1和9),他决定了方法句柄的类型 方法句柄类型的值标识方法句柄的字节码行为 |
||
reference_index | u2 | 值必须是对常量池的有效索引 | ||
CONSTANT_MethodType_info | 表示方法类型 | tag | u1 | 值为16 |
descriptor_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符 | ||
CONSTANT_Dynamic_info | 表示一个动态计算常量 | tag | u1 | 值为17 |
bootstrap_method_attr_index | u2 | 值必须是对当前class文件中引导方法表的bootstrap_methods[]数组的有效索引 | ||
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 | ||
CONSTANT_InvokeDynamic_info | 表示一个动态方法调用点 | tag | u1 | 值为18 |
bootstrap_method_attr_index | u2 | 值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引 | ||
name_and_type_index | u2 |
值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 |
||
CONSTANT_Module_info | 表示一个模块 | tag | u1 | 值为19 |
name_index | u2 | 值必须是对常量池的有效索引,常量池在该处的项必须是CONSTANT_Utf8_info结构,表示模块名称 | ||
CONSTANT_Package_info | 表示一个模块中开放或者导出的包 | tag | u1 | 值为20 |
name_index | u2 | 值必须是对常量池的有效索引,常量池在该处的项必须是CONSTANT_Utf8_info结构,表示包名称 |
二进制对比这个太麻烦了,以后有时间我专门写一篇文章来说具体怎么读.
官方提供了命令[javap]来查看编译后的内容. 常量池对应的字段是[Constant pool]
示例如下:
bogon:~ sysadmin$ javap -verbose JVM.classLast modified 2020-8-19; size 1196 bytesMD5 checksum 5dbdff29b320486831cb3ecc1d488843Compiled from "JVM.java"
public class com.jvm.JVMminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref #15.#43 // java/lang/Object."<init>":()V#2 = Class #44 // com/jvm/JVM#3 = String #45 // hello : 正正正正正正正正正正#4 = Fieldref #46.#47 // java/lang/System.out:Ljava/io/PrintStream;#5 = Methodref #48.#49 // java/io/PrintStream.println:(Ljava/lang/String;)V#6 = Methodref #2.#50 // com/jvm/JVM.sum:(II)I#7 = Class #51 // java/lang/StringBuilder#8 = Methodref #7.#43 // java/lang/StringBuilder."<init>":()V#9 = String #52 // x:#10 = Methodref #7.#53 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#11 = Methodref #7.#54 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;#12 = Methodref #7.#55 // java/lang/StringBuilder.toString:()Ljava/lang/String;#13 = Methodref #2.#43 // com/jvm/JVM."<init>":()V#14 = Methodref #2.#56 // com/jvm/JVM.hello:()V#15 = Class #57 // java/lang/Object#16 = Utf8 ID#17 = Utf8 Ljava/lang/String;#18 = Utf8 ConstantValue#19 = String #58 // 正正正正正正正正正正#20 = Utf8 <init>#21 = Utf8 ()V#22 = Utf8 Code#23 = Utf8 LineNumberTable#24 = Utf8 LocalVariableTable#25 = Utf8 this#26 = Utf8 Lcom/jvm/JVM;#27 = Utf8 hello#28 = Utf8 data#29 = Utf8 sum#30 = Utf8 (II)I#31 = Utf8 a#32 = Utf8 I#33 = Utf8 b#34 = Utf8 c#35 = Utf8 main#36 = Utf8 ([Ljava/lang/String;)V#37 = Utf8 args#38 = Utf8 [Ljava/lang/String;#39 = Utf8 x#40 = Utf8 jvm#41 = Utf8 SourceFile#42 = Utf8 JVM.java#43 = NameAndType #20:#21 // "<init>":()V#44 = Utf8 com/jvm/JVM#45 = Utf8 hello : 正正正正正正正正正正#46 = Class #59 // java/lang/System#47 = NameAndType #60:#61 // out:Ljava/io/PrintStream;#48 = Class #62 // java/io/PrintStream#49 = NameAndType #63:#64 // println:(Ljava/lang/String;)V#50 = NameAndType #29:#30 // sum:(II)I#51 = Utf8 java/lang/StringBuilder#52 = Utf8 x:#53 = NameAndType #65:#66 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#54 = NameAndType #65:#67 // append:(I)Ljava/lang/StringBuilder;#55 = NameAndType #68:#69 // toString:()Ljava/lang/String;#56 = NameAndType #27:#21 // hello:()V#57 = Utf8 java/lang/Object#58 = Utf8 正正正正正正正正正正#59 = Utf8 java/lang/System#60 = Utf8 out#61 = Utf8 Ljava/io/PrintStream;#62 = Utf8 java/io/PrintStream#63 = Utf8 println#64 = Utf8 (Ljava/lang/String;)V#65 = Utf8 append#66 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;#67 = Utf8 (I)Ljava/lang/StringBuilder;#68 = Utf8 toString#69 = Utf8 ()Ljava/lang/String;
{public static final java.lang.String ID;descriptor: Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALConstantValue: String 正正正正正正正正正正public com.jvm.JVM();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/jvm/JVM;public void hello();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=2, args_size=10: ldc #3 // String hello : 正正正正正正正正正正2: astore_13: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;6: aload_17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V10: returnLineNumberTable:line 8: 0line 9: 3line 10: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/jvm/JVM;3 8 1 data Ljava/lang/String;public static int sum(int, int);descriptor: (II)Iflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=20: iload_01: iload_12: iadd3: istore_24: iload_25: ireturnLineNumberTable:line 13: 0line 14: 4LocalVariableTable:Start Length Slot Name Signature0 6 0 a I0 6 1 b I4 2 2 c Ipublic static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=5, args_size=10: iconst_11: istore_12: iconst_23: istore_24: iload_15: iload_26: invokestatic #6 // Method sum:(II)I9: istore_310: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;13: new #7 // class java/lang/StringBuilder16: dup17: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V20: ldc #9 // String x:22: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;25: iload_326: invokevirtual #11 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;29: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;32: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V35: new #2 // class com/jvm/JVM38: dup39: invokespecial #13 // Method "<init>":()V42: astore 444: aload 446: invokevirtual #14 // Method hello:()V49: returnLineNumberTable:line 18: 0line 19: 2line 20: 4line 21: 10line 23: 35line 24: 44line 25: 49LocalVariableTable:Start Length Slot Name Signature0 50 0 args [Ljava/lang/String;2 48 1 a I4 46 2 b I10 40 3 x I44 6 4 jvm Lcom/jvm/JVM;
}
SourceFile: "JVM.java"
六.访问标志
在常量池结束之后, 紧接着的2个字节代表访问标志(access_flags) , 这个标志用于识别一些类或者接口层次的访问信息,
包括: 这个Class是类还是接口; 是否定义为public类型; 是否定义为abstract类型; 如果是类的话, 是否被声明为final; 等等。
access_flags中一共有16个标志位可以使用, 当前只定义了其中9个.没有使用到的标志位要求一律为零。
序号 | 标志名称 | 标志值 | 描述 |
1. | ACC_PUBLIC | 0x0001 | 是否为public类型 |
2. | ACC_FINAL | 0x0010 | 是否被声明为final,只有类可以设置 |
3. | ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义,invokespecial指令的语义在JDK1.0.2发生过改变,为了区别这条指令使用哪种语义,JDK1.0.2之后编译出来的类的这个标志必须为true |
4. | ACC_INTERFACE | 0x0200 | 标识这是一个结构 |
5. | ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志值为true,其他类型值为false |
6. | ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生 |
7. | ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
8. | ACC_ENUM | 0x4000 | 标识这是一个枚举 |
9. | ACC_MODULE | 0x8000 | 标识这是一个模块 |
七.类索引、 父类索引与接口索引集合
类索引(this_class) 和父类索引(super_class) 都是一个u2类型的数据, 而接口索引集合(interfaces) 是一组u2类型的数据的集合, Class文件中由这三项数据来确定该类型的继承关系。
类索引用于确定这个类的全限定名, 父类索引用于确定这个类的父类的全限定名。
由于Java语言不允许多重继承, 所以父类索引只有一个, 除了java.lang.Object之外, 所有的Java类都有父类, 因此除了java.lang.Object外, 所有Java类的父类索引都不为0。 接口索引集合就用来描述这个类实现了哪些接口, 这些被实现的接口将按implements关键字(如果这个Class文件表示的是一个接口, 则应当是extends关键字) 后的接口顺序从左到右排列在接口索引集合中。
类索引、 父类索引和接口索引集合都按顺序排列在访问标志之后, 类索引和父类索引用两个u2类型的索引值表示, 它们各自指向一个类型为CONSTANT_Class_info的类描述符常量, 通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。
八. 字段表集合
字段表(field_info) 用于描述接口或者类中声明的变量。 Java语言中的“字段”(Field) 包括类级变量以及实例级变量, 但不包括在方法内部声明的局部变量。
字段可以包括的修饰符有字段的作用域(public、 private、 protected修饰符) 、 是实例变量还是类变量(static修饰符) 、 可变性(final) 、 并发可见性(volatile修饰符, 是否强制从主内存读写) 、 可否被序列化(transient修饰符) 、 字段数据类型(基本类型、 对象、 数组) 、 字段名称。 各个修饰符都是布尔值, 要么有某个修饰符, 要么没有, 很适合使用标志位来表示。 而字段叫做什么名字、 字段被定义为什么数据类型, 这些都是无法固定的, 只能引用常量池中的常量来描述。
字段表结构:
类型 | 名称 | 含义 | 数量 |
---|---|---|---|
u2 | access_flags | 访问标识 | 1 |
u2 | name_index | 字段的简单名称(引用常量池) | 1 |
u2 | descriptor_index | 方法的描述符(引用常量池) |
1 |
u2 | attributes_count |
用于存储一些额外信息. 字段表可以在属性表中附加描述零至多项的额外信息。 |
1 |
attributes_info | attributes | attributes_count |
字段访问标志
序号 | 标志名称 | 标志值 | 描述 |
1. | ACC_PUBLIC | 0x0001 | 字段是否为public |
2. | ACC_PRIVATE | 0x0002 | 字段是否为private |
3. | ACC_PROTECTED | 0x0004 | 字段是否为protected |
4. | ACC_STATIC | 0x0008 | 字段是否为static |
5. | ACC_FINAL | 0x0010 | 字段是否为final |
6. | ACC_VOLATILE | 0x0040 | 字段是否为volatile |
7. | ACC_TRANSIENT | 0x0080 | 字段是否为transient |
8. | ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动产生 |
9. | ACC_ENUM | 0x4000 | 字段是否为enum |
语法规则的约束:
ACC_PUBLIC、 ACC_PRIVATE、ACC_PROTECTED三个标志最多只能选择其一,
ACC_FINAL、 ACC_VOLATILE不能同时选择。
接口之中的字段必须有ACC_PUBLIC、 ACC_STATIC、 ACC_FINAL标志
概念:
类的全限定名: 仅仅是把类全名中的“.”替换成了“/”而已, 为了使连续的多个全限定名之间不产生混淆, 在使用时最后一般会加入一个“ ; ”号表示全限定名结束。 如: “com/jvm/JVM” 是类JVM的全限定名.
简单名称 : 则就是指没有类型和参数修饰的方法或者字段名称, JVM类中的hello()方法的简单名称就是“hello”.
描述符 : 用来描述字段的数据类型、 方法的参数列表(包括数量、 类型以及顺序) 和返回值。
根据描述符规则, 基本数据类型(byte、 char、 double、 float、 int、 long、 short、 boolean) 以及代表无返回值的void类型都用一个大写字符来表示, 而对象类型则用字符L加对象的全限定名来表示.
用描述符来描述方法时, 按照先参数列表、 后返回值的顺序描述, 参数列表按照参数的严格顺序放在一组小括号“()”之内。
描述符示例:
方法: public static int sum(int, int) 的描述符 为 " (II)I "
方法 : int indexOf(char[]source,int sourceOffset, int sourceCount, char[]target, int targetOffset, int targetCount, int
fromIndex)的描述符为“([CII[CIII)I”。方法: java.lang.String toString()的描述符为“()Ljava/lang/String; ”
方法 void sum()的描述符为“()V”
标识字符 | 含义 | 标识字符 | 含义 |
---|---|---|---|
B | 基本类型 : byte | [ |
数组类型, 每一维度将使用一个前置的“[”字符来描述, 如一个定义 为“java.lang.String[][]”类型的二维数组将被记录成“[[Ljava/lang/String; ”, 一个整型数 组“int[]”将被记录成“[I”。 |
S | 基本类型 : short | ||
I | 基本类型 : int | ||
J | 基本类型 : long | L |
对象类型, 如Ljava/lang/Object |
F | 基本类型 : float |
V |
特殊类型 : void void类型在《Java虚拟机规范》 之中单独列出为“VoidDescriptor”, |
D | 基本类型 : double | ||
Z | 基本类型 : boolean | ||
C | 基本类型 : char |
字段表集合中不会列出从父类或者父接口中继承而来的字段, 但有可能出现原本Java代码之中不存在的字段, 譬如在内部类中为了保持对外部类的访问性, 编译器就会自动添加指向外部类实例的字段。 另外, 在Java语言中字段是无法重载的, 两个字段的数据类型、 修饰符不管是否相同, 都必须使用不一样的名称, 但是对于Class文件格式来讲, 只要两个字段的描述符不是完全相同, 那字段重名就是合法的
九.方法集合表
Class文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式, 方法表的结构如同字段表一样, 依次包括访问标志(access_flags) 、 名称索引(name_index) 、描述符索引(descriptor_index) 、 属性表集合(attributes) 几项.这些数据项目的含义也与字段表中的非常类似, 仅在访问标志和属性表集合的可选项中有所区别。
方法表结构:
类型 | 名称 | 含义 | 数量 |
---|---|---|---|
u2 | access_flags | 访问标识 | 1 |
u2 | name_index | 字段的简单名称(引用常量池) | 1 |
u2 | descriptor_index | 方法的描述符(引用常量池) |
1 |
u2 | attributes_count | 1 | |
attributes_info | attributes | attributes_count |
因为volatile关键字和transient关键字不能修饰方法, 所以方法表的访问标志中没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。
与之相对, synchronized、 native、strictfp和abstract关键字可以修饰方法, 方法表的访问标志中也相应地增加了
ACC_SYNCHRONIZED、 ACC_NATIVE、 ACC_STRICTFP和ACC_ABSTRACT标志。
字段访问标志
序号 | 标志名称 | 标志值 | 描述 |
1. | ACC_PUBLIC | 0x0001 | 方法是否为public |
2. | ACC_PRIVATE | 0x0002 | 方法是否为private |
3. | ACC_PROTECTED | 0x0004 | 方法是否为protected |
4. | ACC_STATIC | 0x0008 | 方法是否为static |
5. | ACC_FINAL | 0x0010 | 方法是否为final |
6. | ACC_SYNCHRONIZED | 0x0020 | 方法是否为synchronized |
7. | ACC_BRIDGE | 0x0040 | 方法是不是由编译器产生的桥接方法 |
8. | ACC_VARARGS | 0x0080 | 方法是否接收不定参数 |
9. | ACC_NATIVE | 0x0100 | 方法是否为native |
10. | ACC_ABSTRACT | 0x0400 | 方法是否为abstract |
11. | ACC_STRICT | 0x0800 | 方法是否为strictfp |
12. | ACC_SYNTHETIC | 0x1000 | 方法是否由编译器自动产生 |
方法里的Java代码, 经过Javac编译器编译成字节码指令之后, 存放在方法属性表集合中一个名为“Code”的属性里面, 属性表作为Class文件格式中最具扩展性的一种数据项目.
与字段表集合相对应地, 如果父类方法在子类中没有被重写(Override) , 方法表集合中就不会出现来自父类的方法信息。 但同样地, 有可能会出现由编译器自动添加的方法, 最常见的便是类构造器“<clinit>()”方法和实例构造器“<init>()”方法[1]。
在Java语言中, 要重载(Overload) 一个方法, 除了要与原方法具有相同的简单名称之外, 还要求必须拥有一个与原方法不同的特征签名。
特征签名是指一个方法中各个参数在常量池中的字段符号引用的集合, 也正是因为返回值不会包含在特征签名之中, 所以Java语言里面是无法仅仅依靠返回值的不同来对一个已有方法进行重载的。 但是在Class文件格式之中, 特征签名的范围明显要更大一些, 只要描述符不是完全一致的两个方法就可以共存。 也就是说, 如果两个方法有相同的名称和特征签名, 但返回值不同, 那么也是可以合法共存于同一个Class文件中的。
十.属性表集合
Class文件、 字段表、 方法表都可以携带自己的属性表集合, 以描述某些场景专有的信息.与Class文件中其他的数据项目要求严格的顺序、 长度和内容不同, 属性表集合的限制稍微宽松一些, 不再要求各个属性表具有严格顺序, 并且《Java虚拟机规范》 允许只要
不与已有属性名重复, 任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。 为了能正确解析Class文件, 《Java虚拟机规范》 最初只预定义了9项所有Java虚拟机实现都应当能识别的属性, 而在最新的《Java虚拟机规范》 的Java SE 12版本中, 预定义属性已经增加到29项.....
虚拟机规范预定义的属性:
序号 | 名称 | 使用位置 | 含义 |
1. | Code | 方法表 | Java代码编译成的字节码指令 |
2. | ConstantValue | 字段表 | 由final关键字定义的常量值 |
3. | Deprecated | 类,方法表,字段表 | 被声明为deprecated的方法和字段 |
4. | Exceptions | 方法表 | 方法抛出的异常列表 |
5. | EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标示这个类所在的外围方法 |
6. | InnerClasses | 类文件 | 内部类列表 |
7. | LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
8. | LocalVariableTable | Code属性 | 方法的局部变量描述 |
9. | StackMapTable | Code属性 | 供类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配. [ JDK6新增 ] |
10. | Signature | 类,方法表,字段表 | 用于支持泛型情况下的方法签名.在Java语言中,任何类,接口,初始化方法或成员的泛型签名如果包含了类型变量(Type Variables) 或参数化类型(Parameterized Types), 则 Signature属性会为它记录泛型签名属性信息. 由于Java的泛型采用擦除编码实现,为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息. [ JDK5 新增 ] |
11. | SourceFile | 类文件 | 记录源文件名称 |
12. | SourceDebugExtension | 类文件 |
SourceDebugExtension属性用于存储额外的调试信息,譬如在进行JSP文件调试时,无法同构Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在Java虚拟机中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension属性就可以用于存储这个标准所新加入的调试信息 . [ JDK6 新增 ] |
13. | Synthetic | 类、方法表、字段表 | 标识方法或字段为编译器自动生成的 |
14. | LocalVariableTypeTable | 类 | 他使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 .[ JDK5 新增 ] |
15. | RuntimeVisibleAnnotations | 类、方法表、字段表 |
为动态注解提供支持。 RuntimeVisibleAnnotations属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的.[ JDK5 新增 ] |
16. | RuntimeInVisibleAnnotations | 类、方法表、字段表 | 与RuntimeVisibleAnnotations属性作用刚好相反,用于指明哪些注解是运行时不可见的.[ JDK5 新增 ] |
17. | RuntimeVisibleParameter Annotations | 方法表 | 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法参数.[ JDK5 新增 ] |
18. | RuntimeInVisibleAnnotations Annotations | 方法表 | 作用与RuntimeInVisibleAnnotations属性类似,只不过作用对象为方法参数.[ JDK5 新增 ] |
19. | AnnotationDefault | 方法表 | 用于记录注解类元素的默认值.[ JDK5 新增 ] |
20. | BootstrapMethods | 类文件 | 用于保存invokedynamic指令引用的引导方法限定符[ JDK7 新增 ] |
21. | RuntimeVisibleTypeAnnotations | 类、方法表、字段表、Code属性 |
指明哪些类注解是运行时(实际上运行时就是进行反射调用)可见的. [ JDK8 新增 ] |
22. | RuntimeInvisibleTypeAnnotations | 类、方法表、字段表、Code属性 |
与RuntimeVisibleTypeAnnotations 相反, 指明哪些类注解是运行时(实际上运行时就是进行反射调用)不可见的.[ JDK8 新增 ] |
23. | MethodParameters | 方法表 |
用于支持(编译时加上 parameter参数)将方法名编译进Class文件中. 并可运行时获取.此前要获取方法名称只能通过JavaDoc中获得. [ JDK8 新增 ] |
24. | Module | 类 |
用于记录一个Module的名称以及相关信息(requires,exports,opens,users,provides) [ JDK9 新增 ] |
25. | ModulePackages | 类 | 用于记录一个模块中所有被exports或者opens的包[ JDK9 新增 ] |
26. | ModuleMainClass | 类 | 用于指定一个模块的主类[ JDK9 新增 ] |
27. | NestHost | 类 | 用于支持嵌套类(Java中的内部类)的放射和访问控制的API,一个内部类通过该属性得知自己的宿主类. [ JDK9 新增 ] |
28. | NestMembers | 类 | 用于支持嵌套类(Java中的内部类)的放射和访问控制的API,一个宿主类通过该属性得知自己有哪些内部类 . [ JDK9 新增 ] |
对于每一个属性, 它的名称都要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示, 而属性值的结构则是完全自定义的, 只需要通过一个u4的长度属性去说明属性值所占用的位数即可。
一个符合规则的属性表的结构
序号 | 类型 | 名称 | 数量 |
1. | u2 | attribute_name_index | 1 |
2. | u4 | attribute_length | 1 |
3. | u1 | info | attribute_length |
Code属性
Java程序方法体里面的代码经过Javac编译器处理之后, 最终变为字节码指令存储在Code属性内。 Code属性出现在方法表的属性集合之中, 但并非所有的方法表都必须存在这个属性, 譬如接口或者抽象类中的方法就不存在Code属性
Code属性是Class文件中最重要的一个属性, 如果把一个Java程序中的信息分为代码(Code, 方法体里面的Java代码) 和元数(Metadata, 包括类、 字段、 方法定义及其他信息) 两部分, 那么在整个Class文件里, Code属性用于描述代码, 所有的其他数据项目都用于描述元数据。
序号 | 类型 | 名称 | 数量 | 含义 |
---|---|---|---|---|
1. | u2 | attribute_name_index | 1 |
指向CONSTANT_Utf8_info型常量的索引, 此常量值固定 为“Code”, 它代表了该属性的属性名称 |
2. | u4 | attribute_length | 1 | 属性值的长度, 由于属性名称索引与属性长度一共为6个字节所以属性值的长度固定为整个属性表长度减去6个字节。 |
3. | u2 | max_stack | 1 | 操作数栈(Operand Stack) 深度的最大值。 在方法执行的任意时刻, 操作数栈都不会超过这个深度。 虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame) 中的操作栈深度。 |
4. | u2 | max_locals | 1 |
局部变量表所需的存储空间 max_locals的单位是变量槽(Slot) , 变量槽是虚拟机为局部变量分配内存所使用的最小单位。 对于byte、 char、float、 int、 short、 boolean和returnAddress等长度不超过32位的数据类型, 每个局部变量占用一个变量槽. double和long这两种64位的数据类型则需要两个变量槽来存放。 方法参数(包括实例方法中的隐藏参数“this”) 、 显式异常处理程序的参数(Exception Handler Parameter, 就是try-catch语句中catch块中所定义的异常) 、 方法体中定义的局部变量都需要依赖局部变量表来存放。 Javac编译器会根据变量的作用域来分配变量槽给各个变量使用, 根据同时生存的最大局部变量数量和类型计算出max_locals的大小。 |
5. | u4 | code_length | 1 |
字节码长度 但是《Java虚拟机规范》 中明确限制了一个方法不允许超过65535条字节码指令, 即它实际只使用了u2的长度, 如果超过这个限制, Javac编译器就会拒绝编译。 |
6. | u1 | code | code_length |
用于存储字节码指令的一系列字节流. 每个指令就是一个u1类型的单字节, 当虚拟机读取到code中的一个字节码时, 就可以对应找出这个字节码代表的是什么指令, 并且可以知道这条指令后面是否需要跟随参数, 以及后续的参数应当如何解析。 目前, 《Java虚拟机规范》 已经定义了其中约200条编码值对应的指令含义. |
7. | u2 | exception_table_length | 1 | |
8. | exception_info | exception_table | exception_table_length | |
9. | u2 | attribute_count | 1 | |
10. | attribute_info | attributes | attribute_count |
注意:
并不是在方法中用了多少个局部变量, 就把这些局部变量所占变量槽数量之和作为max_locals的值, 操作数栈和局部变量表直接决定一个该方法的栈帧所耗费的内存, 不必要的操作数栈深度和变量槽数量会造成内存的浪费。
Java虚拟机的做法是将局部变量表中的变量槽进行重用, 当代码执行超出一个局部变量的作用域时, 这个局部变量所占的变量槽可以被其他局部变量所使用, Javac编译器会根据变量的作用域来分配变量槽给各个变量使用, 根据同时生存的最大局部变量数量和类型计算出max_locals的大小。
在任何实例方法里面, 都可以通过“this”关键字访问到此方法所属的对象。 这个访问机制对Java程序的编写很重要, 而它的实现非常简单, 仅仅是通过在Javac编译器编译的时候把对this关键字的访问转变为对一个普通方法参数的访问, 然后在虚拟机调用实例方法时自动
传入此参数而已。 因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量, 局部变量表中也会预留出第一个变量槽位来存放对象实例的引用, 所以实例方法参数值从1开始计算。 这个处理只对实例方法有效.
Exceptions属性
这里的Exceptions属性是在方法表中与Code属性平级的一项属性.Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Excepitons) , 也就是方法描述时在throws关键字后面列举的异常。
序号 | 类型 | 名称 | 数量 | 含义 |
1. | u2 | attribute_name_index | 1 | |
2. | u4 | attribute_length | 1 | |
3. | u2 | number_of_exceptions | 1 |
方法可能抛出number_of_exceptions种受查异常 每一种受查异常使用一个exception_index_table项表示 |
4. | u2 | exception_index_table | number_of_exceptions | 指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型。 |
LineNumberTable属性
LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量) 之间的对应关系。 它并不是运行时必需的属性, 但默认会生成到Class文件之中, 可以在Javac中使用-g: none或-g: lines选项来取消或要求生成这项信息。 如果选择不生成LineNumberTable属性, 对程序运行产生的最主要影响就是当抛出异常时, 堆栈中将不会显示出错的行号, 并且在调试程序的时候, 也无法按照源码行来设置断点。
序号 | 类型 | 名称 | 数量 | 含义 |
1. | u2 | attribute_name_index | 1 | |
2. | u4 | attribute_length | 1 | |
3. | u2 | line_number_table_length | 1 | |
4. | u2 | line_number_table | line_number_table_length | 一个数量为line_number_table_length、 类型line_number_info的集合, line_number_info表包含start_pc和line_number两个u2类型的数据项, 前者是字节码行号, 后者是Java源码行号. |
LocalVariableTable及LocalVariableTypeTable属性
LocalVariableTable属性用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系, 它也不是运行时必需的属性, 但默认会生成到Class文件之中, 可以在Javac中使用-g: none或-g: vars选项来取消或要求生成这项信息。 如果没有生成这项属性, 最大的影响就是当其他人引用这个方法时, 所有的参数名称都将会丢失, 譬如IDE将会使用诸如arg0、 arg1之类的占位符代替原有的参数名, 这对程序运行没有影响, 但是会对代码编写带来较大不便, 而且在调试期间无法根据参数名称从上下文中获得参数值。
序号 | 类型 | 名称 | 数量 | 含义 |
1. | u2 | attribute_name_index | 1 | |
2. | u4 | attribute_length | 1 | |
3. | u2 | local_variable_table_length | 1 | |
4. | u2 | local_variable_table | local_variable_table_length | 一个栈帧与源码中的局部变量的关联 |
local_variable_info项目结构
序号 | 类型 | 名称 | 数量 | 含义 |
1. | u2 | start_pc | 1 | start_pc和length属性分别代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度, 两者结合起来就是这个局部变量在字节码之中的作用域范围 |
2. | u2 | length | 1 | |
3. | u2 | name_index | 1 |
name_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索 引, 分别代表了局部变量的名称以及这个局部变量的描述符 |
4 | u2 | descriptor_index | 1 | |
5 | u2 | index | 1 | 这个局部变量在栈帧的局部变量表中变量槽的位置。 当这个变量数据类型是64位类型时(double和long) , 它占用的变量槽为index和index+1两个 |
ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。 只有被static关键字修饰的变量(类变量) 才可以使用这项属性。 类似“int x=123”和“static int x=123”这样的变量定义在Java程序里面是非常常见的事情, 但虚拟机对这两种变量赋值的方式和时刻都有所不同。 对非static类型的变量(也就是实例变量) 的赋值是在实例构造器<init>()方法中进行的; 而对于类变量, 则有两种方式可以选择: 在类构造器<clinit>()方法中或者使用ConstantValue属性。
目前Oracle公司实现的Javac编译器的选择是, 如果同时使用final和static来修饰一个变量(按照习惯, 这里称“常量”更贴切) , 并且这个变量的数据类型是基本类型或者java.lang.String的话, 就将会生成ConstantValue属性来进行初始化; 如果这个变量没有被final修饰, 或者并非基本类型及字符串, 则将会选择在<clinit>()方法中进行初始化。
虽然有final关键字才更符合“ConstantValue”的语义, 但《Java虚拟机规范》 中并没有强制要求字段必须设置ACC_FINAL标志, 只要求有ConstantValue属性的字段必须设置ACC_STATIC标志而已, 对final关键字的要求是Javac编译器自己加入的限制。 而对ConstantValue的属性值只能限于基本类型和String这点, 其实并不能算是什么限制, 这是理所当然的结果。 因为此属性的属性值只是一个常量池的索引号, 由于Class文件格式的常量类型中只有与基本属性和字符串相对应的字面量, 所以就算ConstantValue属性想支持别的类型也无能为力。
ConstantValue属性是一个定长属性, 它的attribute_length数据项值必须固定为2。
ConstantValue属性结构
序号 | 类型 | 名称 | 数量 | 含义 |
1. | u2 | attribute_name_index | 1 | |
2. | u4 | attribute_length | 1 | |
3. | u2 | constantvalue_index | 1 | 代表了常量池中一个字面量常量的引用, 根据字段类型的不同, 字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、 CONSTANT_Integer_info和CONSTANT_String_info常量中的一种。 |
这里面属性太多了,我只是挑几个重要的属性罗列一下.. 感兴趣的去看看 周志明写的<深入理解JVM虚拟机>...
参考:
官方: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4
JAVA虚拟机规范 JAVA SE 8版
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)-周志明
[JVM]了断局: Class文件结构梳理相关推荐
- [JVM]了断局: [ 目录 ]
[JVM]了断局: 说什么也没用,背就完了[必背] [JVM]了断局: "运行时数据区"理论梳理 [JVM]了断局: 虚拟机字节码指令表速查 [JVM]了断局: 类文件结构梳理 [ ...
- [JVM]了断局: 局部变量表和操作数栈实例分析
一.前言 本文以两段代码示例来解释说明,JVM在执行类中的方法时,[局部变量表]和[操作数栈]是如何配合工作的. 示例一: 1.代码 package com.classloading;public c ...
- [JVM]了断局: “运行时数据区“理论梳理
Table of Contents 一.前言 二.运行时数据区 2.1.程序计数器 2.2.Java堆 2.3.方法区 2.4.运行时常量池 2.5.直接内存 2.6.Java虚拟机栈 2.7.本地方 ...
- [JVM]了断局: 说什么也没用,背就完了[必背]
Table of Contents 一. JVM在什么情况下会加载一个类 二. 什么时候会初始化一个类 ? 三.类加载器 四.什么情况下JVM内存中的一个对象会被垃圾回收?? 五.如何进入老年代 六. ...
- [JVM]了断局: 堆外内存无法 [ -XX:MaxDirectMemorySize ] 限制
一. 前言 今天看到一句话 , 有点懵, 所以验证一下. 使用sun.misc.Unsafe的allocateMemory方法分配堆外内存.不受-XX:MaxDirectMemorySize这个JVM ...
- [JVM]了断局: G1 入门
一.概念 二.核心设计思路 三.如何设定G1对应的内存大小 四.新生代还有Eden和Survivor的概念吗? 五.G1的新生代垃圾回收 六.什么时候触发新生代+老年代的混合垃圾回收? 七.G1垃圾回 ...
- [JVM]了断局:字节码执行引擎
Table of Contents 一.前言 二.运行时栈帧结构 1.局部变量表 2.操作数栈 3.动态连接 4.方法返回地址 5.附加信息 三.方法调用 1.方法调用 2.解析 一.前言 执行引擎是 ...
- [JVM]了断局:常量池 VS 运行时常量池 VS 字符串常量池
一.前言 最近在看JVM, 常量池, 运行时常量池,字符串常量池 这个看的有点懵. 整理一下. class常量池 是在编译的时候每个class都有的. 在编译阶段,存放的是常量的 符号引用 . ...
- [JVM]了断局:内存模型与线程
一.前言 Java内存模型的主要目的是定义程序中各种变量的访问规则, 即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节. Java内存模型规定了所有的变量都存储在主内存(Main ...
最新文章
- python入门必备指南-致Python初学者 Anaconda入门使用指南完整版
- android释放焦点_Android videoview抢占焦点的处理方法
- python excel centos_centos中使用python遇到的几个问题
- Java快速入门学习笔记7 | Java语言中的类与对象
- Corba传值包含中文解决
- c语言1234为什么不是常量,C语言学习1
- 有什么做电音的软件?3款好用App让你爱上唱歌
- 测试ai模糊软件,“马赛克视频”AI还原软件被疯传!测试效果出乎意料!
- stm32_霍尔编码器
- Steam平台3款2D游戏开发软件对比(转)
- 2020计算机二级office知识点,2020年计算机二级Word常考点汇总
- matlab 控制命令,Matlab绘图基本控制命令
- 英语兔音标学习之双元音
- 08-微服务版单点登陆系统(SSO)实践
- python||报错‘gbk‘ codec can‘t decode byte 0x80 in position 8: illegal multibyte sequence
- python agg函数_个人对Pandas中agg、apply和transform函数的理解
- 3.3.9 反相积分电路
- 学习C++编程的必备软件
- 无线打印服务器评测,打印服务器试用体验
- 红米3s进不了recovery_红米3s卡刷教程_红米3s用recovery刷第三方系统包
热门文章
- 隔夜茶到底能不能喝?
- js实现520倒计时
- 三菱FX3U——ST编程点动与自锁
- Mathorcup数学建模竞赛第六届-【妈妈杯】A题:淡水养殖池塘水华发生及池水自净化研究(附特等奖获奖论文、SAS和matlab代码)
- 5.临床预测模型的构建--cox回归分析
- 【UE4 第一人称射击游戏】22-拾取弹药
- [hbase] hbase写操作时对wal和memstore的操作顺序
- 关于python的数字类型、以描述错误的是_关于Python的数字类型,以下选项中描述错误的是...
- 汇编语言实现两个多位十进制数相减实验
- 用计算机打出小星星,怎样打出一个小星星和更多的符号?