Class文件结构、反射的实现原理与使用

参考:
https://blog.csdn.net/IT_GJW/article/details/80447947
https://zhuanlan.zhihu.com/p/24789506

Class文件在JVM底层的实现

索引:


Class文件.png

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)

  1. 字面量:文本字符串、final 类型的常量值 等
  2. 符号引用:
    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文件的字节码内容,如


TestClass
例: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属性,如


TestClass

反射

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);
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容