简述:
现在正在读一本《 深入理解java虚拟机——JVM高级特性与最佳实践 》的“类文件结构”一章,
阅读笔记《深入理解Java虚拟机——最高实践》
实验
实验:
写了两个简单的类,一个是普通的类A
另外一个是带有static main方法的类Test
A.java
package jvm.class_structure;
public class A {
}
在NotePad++ 其16进制代码
class文件各部分剖析
1. 魔数
头四个字节为Magic Number, 用来确定Class文件能够被虚拟机接受
2. 版本号
第4个字节存储的是Class文件的版本号, 第5和第6个字节是此版本号,第7和第8个是主版本号, Java的版本是从45开始的
然而从1.0 到1.1 是45.0到45.3, 之后就是1.2 对应46, 1.3 对应47 。。。 1.6 对应50,换成16进制就是0x32
3.常量池
在之后就是常量池入口, 是占用Class文件空间最大的数据项目,是一种表类型数据项目。
常量池的索引值从1开始的,如
表示的就是1 ~ 10号索引,0号是用来表示不引用任何一个常量池
之后便是常量池,
常量池:
常量池主要包含字面量和符号引用,
1)字面量比较接近于Java语言层面的常量概念,如文本字符串,被声明为final的常量值等
2)符号引用则属于编译原理方面的概念, 包括
a. 类和接口的权限定名
b. 字段的名称和描述符
c. 方法的名称和描述符
虚拟机运行的时候,从常量池得到对应的符号引用,再在类创建时或运行时解析并翻译到具体的内存地址中
常量池中的每一项常量都是一个表,共有11种不相同的表结构数值
举个例子,
这是实际生成的class文件中的一部分,
第a位, 就是0x0007表示的就是第7种常量类型,为CONSTANT_Class_info,表示 类或接口的符号引用,
其后第b位和第c位0x0002,即指向了常量池中的第2项常量,它指向常量池中一个CONSTATNT_Utf8_info类型的常量,这个常量代表了这个类(或接口)的全限定名
然后查找第d位,0x01表示第1种常量,为CONSTATNT_Utf8_info,
在之后 是这个类的字符串长度是多少,第e为和第f位表示15 就是当前这种常量类型的Byte数目0x000e表示14 即长度为14
4. 访问标志
在常量池之后,有2个字节代表访问标志(access_flags),这个标志用于识别一些类或接口层次的访问信息
包括:这个Class是累还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等
当前使用了8个access_flags(总共两个byte 16位)
ACC_PUBLIC 0x0001
ACC_FINAL 0x0010
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令,1.2之后这个位为真
ACC_INTERFACE 0x0200
ACC_ABSTRACT 0x0400 对于接口或抽象类这个标志值为真
ACC_SYNTHETIC 0x1000 标识这个类并不是有用户代码产生
ACC_ANNOTATION 0x2000
ACC_ENUM 0x4000
在测试类A.class中由于ACC_PUBLIC ,和ACC_SUPER为真
所以他的标志位应该是0x0001 | 0x0020 = 0x0021
5. 类索引和父类索引与接口索引集合
是一个u2类型的数据的集合,class中由这三项的数据7来确定这个类的继承关系。
类索引: 确定这个类的权限定吗
父类索引: 确定这个类的父类的权限定名
接口索引集合: 用来描述这个类实现了哪些接口
上图中第一项类索引为1, 父类索引为3, 由于没有实现任何接口所以第三项接口集合的大小为0
6. 字段表集合
字段表(field_info)用于描述接口或类中声明的变量
字段包括类级变量或实例级变量,但不包括在方法内部声明的变量。
诸如:字段作用域(public、private、protected)、static、final、volatile、transient
字段数据类型(基本类型、对象、数组)、字段名称。
除了各个修饰符都是布尔型的,其他诸如是什么数据类型之类,就由引用常量池中的常量来描述、
如上图,就是字段表从左至右四个部分,分别是:
a. 容量计数器(fields_count) , 若为0x01 , 则表明这个类只有一个字段表数据
b. access_flags ,若为0x01 , 表示为ACC_PUBLIC为真,为public
c. 字段表名称(name_index), 这里为0x05, 为CONST_Utf8_info类型字符串, 表示变量名称
d. 字段描述符(descriptor_index) , 这里为0x06, 指向常量池中的字符串'I' 表示一个int的变量
7. 方法表
依次包括:
访问标志、名称索引、描述符索引、属性表集合
其中方法中的代码是放在属性表中一个Code的属性中
附:
因为volatile和transient关键字不能修饰方法,所以方法表的访问标志中没有了这两项
但是增加了synchronized,native,strictfp, abstract的访问标志位ACC_*(' * ' 代指这四项)
8. 属性表
属性表集合的限制较为宽松, 不再要求各个属性表具有严格的顺序,并且只要无命名重复,可以自定义写入属性表的信息。
Java虚拟机运行时会忽略掉他不认识的属性
1) Code 属性
Java方法体里面的代码经过Javac编译之后,最终变为字节码指令存储在Code属性内,Code属性出现在方法表的属性集合中,但在接口或抽象类中就不存在Code属性
2)Exception属性
列举出方法中可能抛出的受查异常
3)LineNumberTable属性
描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。主要是如果抛出异常时,编译器会显示行号,就是这个属性的作用
4)LocalVariableTable属性
描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。用处在于当别人使用这个方法是能够显示出方法定义的参数名
5)SourseFile属性
记录生成这个Class文件的源码文件名称
抛出异常时能够显示错误代码所属的文件名
6)ConstantValue属性
通知虚拟机自动为静态变量赋值,只有被static字符修饰的变量(类变量)才可以有这项属性
7)InnerClass属性
用于记录内部类与宿主类之间的关联
8、9)Deprecated和Synthetic属性
这两个都是标志类型的布尔属性
Deprecated表示不再推荐使用,注解表示为@deprecated
Synthetic表示此字段或方法是由编译器自行添加的
这篇阅读笔记走马观花,浅尝辄止,后面还需要继续研究