美文网首页工作学习笔记
Class文件结构、反射的实现原理与使用

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

作者: Megahorn | 来源:发表于2019-06-22 18:08 被阅读0次
    参考:
    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);
        }
    
    }
    

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/rrzuqctx.html