转载

【JVM】或许,这就是二进制Class吧

水稻:看你研究盯着这个文档一天了,什么玩意让人心驰神往

菜瓜:前几天意外得到一本武功秘籍《jvms8》,看起来就情不自禁

水稻:这不是Java虚拟机的说明文档吗<PS:投来惊吓的目光>

菜瓜:是的,在研究第四章-The class File Format. 讲的是class文件结构。以前模糊的知道我们写的java代码是以二进制字节码加载到虚拟机然后执行的,但是没有见识过

水稻:有什么收获,分享一下啊

菜瓜:只是在研究,可以一起探讨。我是这么干的,先准备工具

  • jvms8官方文档下载 - (因为我是用jdk8编译的,所以下载的是8版本的。可以选择自己的版本 https://docs.oracle.com/javase/specs/index.html )
  • idea插件jclasslib Bytecode viewer - (代替javap命令直接在idea中查看字节码编译内容)
  • sublime - 查看16进制字节码,方便阅读 (也可以下载idea插件BinEd)

菜瓜:写一段最简单的demo

  • java源文件
    • package club.interview.jvm;
      
      /**
       * @author QuCheng on 2020/7/10.
       */
      public class ClassOriginal {
      }
      
  • 二进制 - 编译成class文件
  • 16进制 
    • cafe babe 0000 0034 0010 0a00 0300 0d07
      000e 0700 0f01 0006 3c69 6e69 743e 0100
      0328 2956 0100 0443 6f64 6501 000f 4c69
      6e65 4e75 6d62 6572 5461 626c 6501 0012
      4c6f 6361 6c56 6172 6961 626c 6554 6162
      6c65 0100 0474 6869 7301 0022 4c63 6c75
      622f 696e 7465 7276 6965 772f 6a76 6d2f
      436c 6173 734f 7269 6769 6e61 6c3b 0100
      0a53 6f75 7263 6546 696c 6501 0012 436c
      6173 734f 7269 6769 6e61 6c2e 6a61 7661
      0c00 0400 0501 0020 636c 7562 2f69 6e74
      6572 7669 6577 2f6a 766d 2f43 6c61 7373
      4f72 6967 696e 616c 0100 106a 6176 612f
      6c61 6e67 2f4f 626a 6563 7400 2100 0200
      0300 0000 0000 0100 0100 0400 0500 0100
      0600 0000 2f00 0100 0100 0000 052a b700
      01b1 0000 0002 0007 0000 0006 0001 0000
      0006 0008 0000 000c 0001 0000 0005 0009
      000a 0000 0001 000b 0000 0002 000c 
      
  • 引用jvms8中的说明 - 虚拟机按照这个结构体对二进制文件进行解析
    • A class file consists of a single ClassFile structure: //单个class文件结构组成
      ClassFile {
          u4 magic; // 前4字节magic
          u2 minor_version; // jdk小版本
          u2 major_version; // 主要版本
          u2 constant_pool_count; // 常量池大小
          cp_info constant_pool[constant_pool_count-1]; // 常量池信息
          u2 access_flags; // 类访问修饰符
          u2 this_class; // 当前class指向常量池
          u2 super_class; // 父类class指向常量池
          u2 interfaces_count; // 接口总数 
          u2 interfaces[interfaces_count]; // 接口索引 - 若无接口,则无需统计
          u2 fields_count; // 字段统计
          field_info fields[fields_count]; // 字段信息 - 若无成员字段,则无需统计
          u2 methods_count; // 方法统计
          method_info methods[methods_count]; // 方法信息 - 默认会有无参构造
          u2 attributes_count; // 属性
          attribute_info attributes[attributes_count]; // 表示文件信息,譬如路径和文件名
      }
      
      
    • u4、u2 -- u表示无符号位,4和2表示字节数

水稻:cafe babe 眼熟 ,0034转换为10进制是52,代表1.8版本。后面的倒是没见过

菜瓜:那我们看一拿着前面一段来对照看看

  • cafe babe 0000 0034 0010 0a00 0300 0d07
    
    按照结构体来划分
    u4 4个字节 <cafe babe>  头文件校验
    u2 2个字节 <0000> minor_version小版本=0
    u2 2个字节 <0034> major_version大版本 52(16进制) = 1.8
    u2 2个字节 <0010> 常量池大小 16(16进制)
    

水稻:懂,后面的 0a00 0300 0d07 是什么呢? 按照结构体的排序是cp_info 常量池数组根据下标编排的内容对吧

菜瓜:没错,这里的cp_info 结构体有一个参照表如下

  • Constant_Type Value
    CONSTANT_Class 7 
    CONSTANT_Fieldref 9 
    CONSTANT_Methodref 10 
    CONSTANT_InterfaceMethodref 11 
    CONSTANT_String 8 
    CONSTANT_Integer 3 
    CONSTANT_Float 4 
    CONSTANT_Long 5 
    CONSTANT_Double 6 
    CONSTANT_NameAndType 12 
    CONSTANT_Utf8 1 
    CONSTANT_MethodHandle 15 
    CONSTANT_MethodType 16 
    CONSTANT_InvokeDynamic 18
    
    
    info 也是一个对象,不同对象属性还不一样
    // tag=10 方法引用
    CONSTANT_Methodref_info { 
            u1 tag;
            u2 class_index; // class下标
            u2 name_and_type_index; // 
    }
    // tag = 7
    CONSTANT_Class_info { 
            u1 tag;
            u2 name_index;  // class索引
    }
    
    // tag = 1
    CONSTANT_Utf8_info { 
            u1 tag;
            u2 length;
        u1 bytes[length];
    }
    
    CONSTANT_NameAndType_info { 
            u1 tag;
            u2 name_index;
            u2 descriptor_index; 
    }
    
  • 再来看上面没解析的  0a00 0300 0d07以及后续
    • 0a00 0300 0d07
      
      u1 1字节 0a 表示10 对应10号结构体
      CONSTANT_Methodref_info { 
              u1 tag;
              u2 class_index; // class下标
              u2 name_and_type_index; // 结构体下标
      }
      
      u1 0a tag 结构体cp_info标识
      u2 00 03  class_index 类文件下标,此处指向常量池03位
      u2 00 0d  name_type_index 指向常量池13位
      
  • 再列举几个,你应该就能看明白了
    • 衔接第一排0d07
      000e 0700 0f01 0006 3c69 6e69 743e 0100
      0d已经被使用,从07开始
      对照cp_info 07号结构体
      // tag = 7
      CONSTANT_Class_info { 
              u1 tag;
              u2 name_index;  // class索引
      }
      <07 000e>
          u1 07 
          u2 000e  指向常量池第14位
      <0700 0f>
          u1 07
          u2 000f  常量池第15位
      
      下一位01 对应结构体
      // tag = 1
      CONSTANT_Utf8_info { 
          u1 tag;
          u2 length;
              u1 bytes[length];
      }
      <01 0006 3c69 6e69 743e>
          u1 tag
          u2 0006 后续字节长度
          u1 长度为6的字节数组  (3c69 6e69 743e) 对照ASCII表翻译成字符("<init>")    
      

水稻:妙啊!你怎么证明是这样的

菜瓜:不慌,用到第二个idea插件工具jclasslib ... 怎么使用我就不演示了。(也可以使用javap命令)编译结果如下

  • 类整体结构信息
    • 【JVM】或许,这就是二进制Class吧
  • 常量池信息

    • 【JVM】或许,这就是二进制Class吧
    • 【JVM】或许,这就是二进制Class吧

    • 【JVM】或许,这就是二进制Class吧

    • 【JVM】或许,这就是二进制Class吧

  • 后面的我就不贴了

水稻:原来是这样解析的!!

菜瓜:当然这个其实没什么技术含量,只是一个比较死板的解析过程而已,不过这个设计真让人拍案叫绝,只要最后java文件能被编译成这种class文件格式,jvm就都能解析。后面有个区域需要熟悉一下:methods

水稻:哦?有什么讲究

菜瓜:我们方法的执行逻辑都在这里,有个i++和++i的常见面试题可以从这里一探究竟。因为我写的这个demo比较简单,此处的mthods区域只有一个方法就是默认的构造方法

  • 【JVM】或许,这就是二进制Class吧
  • 要想看懂这个,还得拿jvms8的指令集对照表查看- 第6章6.5
    • aload_0 将this从局部变量表加载到操作数栈栈顶  (aload_0指令 - 16进制码是2a)
    • invokespecial 调用方法 - 这里是调用的object的构造方法 (invokespecial指令 - 16进制码是b7)
    • return - 返回结构 (return - 16进制是 b1)
  • 后面标注了指令对应的16进制码,可以呼应上面我们的16进制对照表
    • 2a b7 b1

水稻:有收获,虽然对写代码没啥太大用,但是这个流程搞清楚了就比较通透

菜瓜:后面我还想继续深入一下,有收获再分享啊

水稻:可以可以

总结:

  • 了解.java文件被javac指令编译后的字节码是如何解析的
  • 文中demo过于简单,此处只是为了演示方便。上面有提到i++和++i的面试题,大伙可以自己编译去看看( https://www.cnblogs.com/nightOfStreet/p/13253792.html )
原文  http://www.cnblogs.com/nightOfStreet/p/13256627.html
正文到此结束
Loading...