• OpenJDK源码研究笔记(八)-详细解析如何读取Java字节码文件(.class)


    在上一篇OpenJDK源码研究笔记(七)–Java字码文件(.class)的结构中,我们大致了解了Java字节码文件的结构。

    本篇详细地介绍了如何读取.class文件的大部分细节。

    1.构造文件 

     // 字节码文件User.class
      String userClass = "C:/User.class";
      File file = new File(userClass);
    


    2.构造输入流

    FileInputStream fin = new FileInputStream(file);
    
    DataInputStream in= new DataInputStream(new BufferedInputStream(fin));
    
    


    3.读取字节码文件(.class)的内容

       3.1魔法数

    //无符号4个字节
     int   magic = in.readInt();


       3.2次版本号

         //无符号2个字节
         int   minor_version = in.readUnsignedShort();

       3.3主版本号

     //无符号2个字节
          int  major_version = in.readUnsignedShort();
    


      3.4常量池的个数
          //无符号2个字节
           int count = cr.readUnsignedShort();

      3.5常量池的内容

    //CPInfo是常量池中的常量的类型,不能简单地把常量池中的类型理解为String类型
    
       CPInfo[]   pool = new CPInfo[count];
      for (int i = 1; i < count; i++) {
       // 常量池中常量的类型标记
       int tag = cr.readUnsignedByte();
       switch (tag) {
       // 类或接口的符号引用
       case CONSTANT_Class:
        pool[i] = new CONSTANT_Class_info(this, cr);
        break;
       // 双精度浮点型字面量
       case CONSTANT_Double:
        pool[i] = new CONSTANT_Double_info(cr);
        i++;
        break;
       // 字段的符号引用
       case CONSTANT_Fieldref:
        pool[i] = new CONSTANT_Fieldref_info(this, cr);
        break;
       // 浮点型字面量
       case CONSTANT_Float:
        pool[i] = new CONSTANT_Float_info(cr);
        break;
       // 整型字面量
       case CONSTANT_Integer:
        pool[i] = new CONSTANT_Integer_info(cr);
        break;
       // 接口方法的引用
       case CONSTANT_InterfaceMethodref:
        pool[i] = new CONSTANT_InterfaceMethodref_info(this, cr);
        break;
       // 动态调用指令
       case CONSTANT_InvokeDynamic:
        pool[i] = new CONSTANT_InvokeDynamic_info(this, cr);
        break;
       // 长整型字面量
       case CONSTANT_Long:
        pool[i] = new CONSTANT_Long_info(cr);
        i++;
        break;
       // 方法处理器
       case CONSTANT_MethodHandle:
        pool[i] = new CONSTANT_MethodHandle_info(this, cr);
        break;
       // 方法类型
       case CONSTANT_MethodType:
        pool[i] = new CONSTANT_MethodType_info(this, cr);
        break;
       // 方法引用
       case CONSTANT_Methodref:
        pool[i] = new CONSTANT_Methodref_info(this, cr);
        break;
       // 名字和类型
       case CONSTANT_NameAndType:
        pool[i] = new CONSTANT_NameAndType_info(this, cr);
        break;
       // 字符串
       case CONSTANT_String:
        pool[i] = new CONSTANT_String_info(this, cr);
        break;
       // Utf8编码的字符串
       case CONSTANT_Utf8:
        pool[i] = new CONSTANT_Utf8_info(cr);
        break;
       // 不合法的类型
       default:
        throw new InvalidEntry(i, tag);
       }
      }


       构造细节

       举一个例子,比如构造Double类型的常量结构CONSTANT_Double_info,就是在这个结构中保存一个Double的值。

       此外,还维护了一些其它信息,这个结构的字节长度、类型标记。

      其它一些结构,类似。

       3.6访问标识符

     //无符号2个字节
    
        int access_flag= cr.readUnsignedShort()


    3.7这个类在常量池中的下标

    //无符号2个字节
         int   this_class = in.readUnsignedShort();


    3.8这个类的父类在常量池中的下标

      //无符号2个字节
         int   super_class = in.readUnsignedShort();


       3.9父接口的数量

        //无符号2个字节

         int   int interfaces_count = cr.readUnsignedShort();

        3.10父接口的内容

    interfaces = new int[interfaces_count];
    
         for (int i = 0; i < interfaces_count; i++){
    
               //父接口的类型,无符号2个字节
                interfaces[i] = cr.readUnsignedShort();
    
        }


       3.11字段的数量

    //无符号2个字节
    
        int    int fields_count = cr.readUnsignedShort();


    3.12字段的内容

    fields = new Field[fields_count];
       for (int i = 0; i < fields_count; i++){
                fields[i] = new Field(cr);
       }
    
    //一个字段Field有访问标识符、名字、描述符、属性
    
          access_flags = cr.readUnsignedShort();
            name_index = cr.readUnsignedShort();
            descriptor = cr.readUnsignedShort()
    
             attributes,封装了字段的更多详细信息
     3.13方法的数量,无符号2个字节
     int methods_count = cr.readUnsignedShort();
    

          3.14方法的内容

     methods = new Method[methods_count];
    
       for (int i = 0; i < methods_count; i++)
                methods[i] = new Method(cr);
    
         //   一个方法Method有访问标识符、名字、描述符、属性
    
            access_flags = cr.readUnsignedShort();
            name_index = cr.readUnsignedShort();
            descriptor = cr.readUnsignedShort()
    
             attributes,封装了方法的更多详细信息
    
      


           3.15属性的数量

     //无符号2个字节
    
            int attrs_count = cr.readUnsignedShort();
    
    


           3.16属性的内容

            

    Attribute[]  attrs = new Attribute[attrs_count];
            for (int i = 0; i < attrs_count; i++) {
                   attrs[i] =  readAttribute();
    
             }
    
    /**
      * 读取一个属性Attribute
      */
     public Attribute readAttribute() throws IOException {
      //名字的索引
      int name_index = readUnsignedShort();
      //属性内容的长度
      int length = readInt();
      byte[] data = new byte[length];
      //读取length个长度的字节信息到data中
      readFully(data);
    
      DataInputStream prev = in;
      in = new DataInputStream(new ByteArrayInputStream(data));
      try {
       //根据名字的索引、字节数组形式的内容构造一个Attribute对象
       return attributeFactory.createAttribute(this, name_index, data);
      } finally {
       in = prev;
      }
     }


    4.总结

    一个字节码文件(.class)几乎含有一个类或接口的所有信息。

    这些信息虽然以二进制形式存在,但是它们的存储结构仍然是有规律的。

    按照存储格式来依次读取相应的字节数目,就可以完整地解析一个字节码文件。

    5.挑战极限

    讲完了字节码文件的的结构和读取字节码文件的方法,有读者可能会问,字节码文件是怎么生成的?

    我们写的Java程序都是文本格式的.java文件,它们是如何转换的呢?

    其实,.java到.class的转换过程,就是Java编译器javac的主要功能。

    Java编译器javac比较有难度,刚刚开始研究,如有心得体会和研究成功,一定第一时间分享出来。

     

    6.相关阅读

    OpenJDK源码研究笔记(一)-参数检查&抛出带关键错误提示信息的异常

    OpenJDK源码研究笔记(二)-Comparable和Comparator2个接口的作用和区别(一道经典的Java笔试面试题)

    OpenJDK源码研究笔记(三)-RandomAccess等标记接口的作用

    OpenJDK源码研究笔记(四)-编写和组织可复用的工具类和方法

    OpenJDK源码研究笔记(五)-缓存Integer等类型的频繁使用的数据和对象,大幅度提升性能(一道经典的Java笔试题)

    OpenJDK源码研究笔记(六)--观察者模式工具类(Observer和Observable)和应用示例

    OpenJDK源码研究笔记(七)–Java字节码文件(.class)的结构

    原文参见http://FansUnion.cn/articles/2974

  • 相关阅读:
    vue3 祖孙传递数据
    vue 导航栏不能收缩的问题
    vue 项目中的问题
    Python第一周Round1记录
    [转]80端口被系统占用pid=4: NT kernel & System
    表<表名称> 中的列与现有的主键或UNIQUE约束不匹配
    一些算法(2)
    卸载不了java(tm)se development kit 7 update 3
    如何解决 Eclipse中出现访问限制由于对必需的库XX具有一定限制,因此无法访问类型
    [COPY]Eclipse无法导入项目
  • 原文地址:https://www.cnblogs.com/qitian1/p/6463502.html
Copyright © 2020-2023  润新知