参考:
https://blog.csdn.net/IT_GJW/article/details/80447947
https://zhuanlan.zhihu.com/p/24789506
Class文件在JVM底层的实现
索引:
Class文件是一组以8位字节为基础单位的二进制流,
各个数据项目按严格的顺序紧凑的排列在Class文件中,
里面的信息主要描述以下信息:
1. 魔数和版本号
1.1. 魔数:0xCAFEBABE,确定这个文件是否为一个能被虚拟机接收的Class文件
1.2. Class文件的版本号:第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。
2. 常量池
主要存放字面量(Literal)和符号引用(references)
- 字面量:文本字符串、final 类型的常量值 等
- 符号引用:
a. 类和接口的全限定名
b. 字段描述和描述符
c. 方法的名称和描述
Java代码在进行Javac编译时,并不像C和C++那样有“连接”的步骤,而是在虚拟机加载Class文件的时候进行动态连接。
也就是说,
在Class文件中不会保存各个方法、字段的最终内存布局信息,
当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
JDK1.7中,总共有14种类型的常量,每种常量都是表类型的数据项。
这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位,代表当前常量属于哪种常量类型。
14种常量类型代表的具体含义见下表
类型 | 标志 | 描述 |
---|---|---|
Constant_Utf8_info | 1 | UTF-编码的字符串 |
Constant_Integer_info | 3 | 整型字面量 |
Constant_Float_info | 4 | 浮点型字面量 |
Constant_Long_info | 5 | 长整型字面量 |
Constant_Double_info | 6 | 双精度浮点型字面量 |
Constant_Class_info | 7 | 类或接口的符号引用 |
Constant_String_info | 8 | 字符串类型字面量 |
Constant_Fieldref_info | 9 | 字段的符号引用 |
Constant_Methodref_info | 10 | 类中方法的符号引用 |
Constant_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
Constant_NameAndType_info | 12 | 字段或方法的部分符号引用 |
Constant_MethodHandle_info | 15 | 表示方法句柄 |
Constant_MethodType_info | 16 | 标识方法类型 |
Constant_InvodeDynamic_info | 18 | 表示一个动态方法调用点 |
可以通过用命令javap -verbose TestClass.class命令查看class文件的字节码内容,如
例:Constant_Methodref_info的结构
tag | index | index |
---|---|---|
u1 | u2 | u2 |
标志位,值如上表,10 | 指向生命方法的类描述符Constant_Class_info的索引项 | 指向名称及类型描述符Constant_NameAndType_info的索引项 |
10 | 例:0x0004 | 例:0x000f |
例:Constant_Class_info的结构
tag | index |
---|---|
u1 | u2 |
标志位,值如上表,7 | 指向全限定名常量项的索引项 |
7 | 例:0x0011 |
例:Constant_Utf8_info的结构
tag | length | bytes |
---|---|---|
u1 | u2 | u1 |
标志位,值如上表,1 | UTF-8编码的字符串占用的字节数 | 长度为length的UTF-8编码的字符串 |
1 | 例:java/lang/Object |
3. 当前类的访问标志:
常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息
a.这个Class是类还是接口
b.这个Class是否是public 等类型
c.这个Class是否是abstract ,是否被声明为final 等标志
4. 类索引、父类索引和接口索引集合
a.类索引:确定这个类的全限定名
b.父类索引:确定父类的全限定名
c.接口索引集合:描述这个类实现了哪些接口,它是一组u2类型的数据的集合,集合中的第一项是接口计数器,表示索引表的容量。如果一个类没有实现任何接口,则该计数器值为0。
5. 字段表集合(Fileds)
用于描述接口或者类中声明的变量。
包括信息有
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | access_flags | 1 | 字段作用域(public,private等修饰符) 是实例变量还是类变量(static) 可变性 (final) 并发可见性(volatile) 可否被序列化(transient)等信息 |
u2 | name_index | 1 | 对常量池的引用 代表字段的简单名称 |
u2 | descriptor_index | 1 | 对常量池的引用 代表字段的描述符 描述字段的数据类型 |
u2 | attribute_count | 1 | 计数器 如果其值为0:字段没有额外信息 如果其值不为0:则attribute_count后面会紧跟着attribute_count个attribute数据项。 |
attribute_info | attributes | attributes_count |
6. 方法表集合:
包括
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | access_flags | 1 | 访问标志 |
u2 | name_index | 1 | 名称索引 |
u2 | descriptor_index | 1 | 描述符索引 |
u2 | attributes_count | 1 | 属性表集合 方法里的Java代码经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性表中。属性表作为class文件格式中最具有扩展性的一种数据项目,将在随后介绍。 |
attribute_info | attributes | attributes_count |
7. 其他:包括属性表集合、Code 属性(指令) 等。
属性表(attribute_info)在前面的讲解中已经出现过多次了,字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,
**属性表集合的限制稍微宽松了一些,不再要求各个属性表具有严格的顺序。
为了能够正确解析Class文件,《Java虚拟机规范(Java SE 7)》中,预定义了21项属性表。下文将对一些常用的属性表进行讲解。
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才能拥有这个属性 这个属性用于标识这个类所在的外围方法 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LineVariableTable | Code属性 | 方法的局部变量描述 |
StackMapTable | Code属性 | |
Signature | 类、方法表、字段表 | |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | |
Synthetic | 类、方法表、字段表 | |
LocalVariableTypeTable | 类 | |
RuntimeVisibleAnnotations | 类、方法表、字段表 | |
RuntimeInvisibleAnnotations | 类、方法表、字段表 | |
RuntimeVisibleParameter | 方法表 | |
RuntimeInvisibleParameter | 方法表 | |
AnnotationDefault | 方法表 | |
BootstrapMethod | 类文件 |
Code属性
Code属性存储编译后的字节码指令,它出现在方法表的属性集合中,
但并非所有方法都必须存在这个属性,譬如抽象方法就不存在Code属性。
Code属性的结构如下表所示
类型 | 名称 | 数量 | |
---|---|---|---|
u2 | attribute_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 值固定为“Code” 代表该属性的属性名称 attribute_length指示了属性值的长度 |
u4 | attribute_length | 1 | |
u2 | max_stack | 1 |
操作数栈的最大值 在方法执行的任意时刻 操作数栈都不会超过这个深度 虚拟机运行时需要根据这个值来分配栈帧中操作栈深度 |
u2 | max_local | 1 | 局部变量表所需的存储空间 它的单位是Slot |
u4 | code_length | 1 | 字节码长度 |
u1 | code | code_length | 存储字节码指令的一系列字节流 每个指令就是一个u1类型的单字节 u1类型的取值范围是0x00~0xFF 一共可以表达256条指令 |
u2 | exception_table_length | 1 | |
exception_info | exception_table | exception_table_length | |
u2 | attributes_count | 1 | |
attribute_info | attributes | attributes_count |
可以通过用命令javap -verbose TestClass.class命令查看一个class文件中方法的Code属性,如
反射
Java反射机制就是在运行状态中,
对于任意一个类,都能够知道这个类的属性和方法。
对于任意一个对象能够调用它的任意一个属性和方法。
这种动态获取的信息和动态调用对象的方法的功能称为Java语言的反射机制
反射机制就是通过Class类实现的。
在Java中,Object 类是所有类的根类,而Class类就是描述Java类的类。
在Java中,每一个class都有一个相应的Class对象,
在将Java源码编译成.class文件中就会生成一个Class对象,
Class对象表示这个类的类型信息,你也可以理解成Class是类的类型
注意:因为Class类也是类,所以Object也包括Class类
我们创建对象一般是通过new关键字创建,
但是new是静态加载类,一旦找不到类就会编译不通过。
但是通过反射机制创建对象一旦找不到类则抛出java.lang.ClassNotFoundException异常。
Class对象的常用方法:
Constructor[] getConstructors():返回此Class对象所表示的类的所有public构造方法
Method[] getMethods():返回此Class对象所表示的类的所有public方法
Method[] getDeclaredMethods():返回此Class对象所表示的类的所有方法,与方法的访问级别无关
Field[] getFields():返回此Class对象所表示的类的所有public属性
Field[] getDecalaredDields():返回此Class对象所表示的类的所有属性,与属性访问级别无关
Object get(Object obj):得到引用类型属性值
void set(Object obj,Object val):将obj对象的该属性设置成val值。针对引用类型赋值
Object invoke(Object obj,Object args):调用类的方法,obj是执行该方法的对象,args是执行该方法时传入该方法的参数
通过Class类得到指定的对象:
public interface Office {
/**
* 描述
*/
public void describe();
}
public class Word implements Office {
@Override
public void describe() {
System.out.println("大家好,我是Word");
}
}
public class Test {
public static void main(String[] string) throws ClassNotFoundException {
try {
@SuppressWarnings("rawtypes")
//传入接口实现类的路径
Class office = Class.forName("com.Word");
try {
//创建该类的对象
Office word = (Office)office.newInstance();
//调用方法
word.describe();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
通过对象得到Class类
public class Test {
@SuppressWarnings("rawtypes")
public static void main(String[] string) throws ClassNotFoundException {
Word word = new Word();
Class word1 = word.getClass(); // 通过对象的getClass()方法获取Class
Class word2 = Word.class; // 通过类.class获取Class
Class word3 = Class.forName("com.Word"); // 通过路径获取Class
System.out.println(word1 == word2);
System.out.println(word2 == word3);
}
}