【深入理解JVM-1】JVM字节码文件剖析

【深入理解JVM-1】JVM字节码文件剖析

    • 一、背景
    • 二、二进制字节码文件分析
      • 1. 使用javap -verbose 命令分析字节码文件
        • 1.1 分析字节码文件的魔数、版本号、常量池
        • 1.2 常量池
        • 1.3 AccessFlag访问标志、当前类名、当前父类名、当前接口数量
        • 1.4 字段表结构
        • 1.5 方法结构
        • 1.6 附加属性
      • 2. 方法表结构

一、背景

    

二、二进制字节码文件分析

1. 使用javap -verbose 命令分析字节码文件

Java字节码整体结构如下所示:
【深入理解JVM-1】JVM字节码文件剖析_第1张图片

1.1 分析字节码文件的魔数、版本号、常量池

  • 工具:Hex_Fiend,下载地址
  • 以单字节为间隔展示文件如下:
    【深入理解JVM-1】JVM字节码文件剖析_第2张图片
  • 魔数:CA FE BA BE,所有的.class字节码文件的前4字节都是魔数,固定值为0xCAFEBABE
  • 版本号:00 00 00 31,前面两字节为次版本号0,后面两字节为主版本号49

1.2 常量池

  • 常量池( constant pool):紧接着主版本号之后的即常量池。一个Java类中定义的很多信息都是由常量池来维护和描述的,可将常量池看作是class文件的资源仓库,例如Java类中定义的方法与变量信息,均存储在常量池中。常量池中主要存储两类信息:字面量、符号引用。
    • 字面量:文本字符串、Java类中声明为final的常量值等
    • 符号引用:类和接口的全局限定名、字段的名称和描述符、方法的名称和描述符(与源码的对应关系)
    • 常量池总体结构:Java类所对应的常量池主要由常量池数据量(紧跟主版本号后面,占2字节)与常量池数组( 紧跟常量池数量后面)两部分共同构成。常量池数组中不同元素的类型、结构均不同,但是每个元素的第一个数据都是一个u1类型,该字节是个标志位,占1个字节,JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。
    • 常量池数组中元素个数=常量池数-1(0暂不使用),满足某些常量池索引值的数据表达为【不引用任何一个常量池】的含义
    • 如图,00 18代表常量池数量为23。

【深入理解JVM-1】JVM字节码文件剖析_第3张图片
【深入理解JVM-1】JVM字节码文件剖析_第4张图片

  • 如上图,标注为第一个常量池,0A为1字节的tag,值为10,代表CONSTANT_Methodref_info,
  • 00 04为2字节的index,值为4,指向声明方法的类描述符,即如下的“#4 = Class #23 // java/lang/Object”
  • 00 14为2字节的index,值为20,指向名称及类型描述符,即如下的“#20 = NameAndType #7:#8 // “”)V”,指向#7和#8,方法名称为构造方法,类型描述符为()V。十六进制可以直接通过ASCLL转换成具体的字符
STARLYWANG-MB0:classes starlywang$ javap -verbose com.bytecode.MyTest1
Classfile /Users/starlywang/IdeaProjects/comtest/target/classes/com/bytecode/MyTest1.class
  Last modified 2020-3-1; size 471 bytes
  MD5 checksum c75cdac216156e899c9491c4738e0b60
  Compiled from "MyTest1.java"
public class com.bytecode.MyTest1
  minor version: 0 //小版本
  major version: 49  //大版本
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:  //常量池
   #1 = Methodref          #4.#20         // java/lang/Object."":()V
   #2 = Fieldref           #3.#21         // com/bytecode/MyTest1.a:I
   #3 = Class              #22            // com/bytecode/MyTest1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/bytecode/MyTest1;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest1.java
  #20 = NameAndType        #7:#8          // "":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               com/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
     
  public com.bytecode.MyTest1(); //构造方法
    descriptor: ()V  //描述符
    flags: ACC_PUBLIC  //标记
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 9: 0
        line 10: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/bytecode/MyTest1;

  public int getA();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: ireturn
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/bytecode/MyTest1;

  public void setA(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field a:I
         5: return
      LineNumberTable:  //行号表
        line 17: 0
        line 18: 5
      LocalVariableTable:  //局部变量表
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

  • JVM规范中,每个变量/字段斗鱼描述信息,主要作用是描述字段的数据类型、方法的参数列表(包括数量、类型与顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,对象类型则使用字符L加对象全限定名称来表示。
  • 为了压缩字节码文件的体积,JVM只使用一个大写字母来表示,如下所示:B-byte、C-char、D-double、F-float、I-int、J-long、S-short、Z-boolean、V-void、L-对象类型,如Ljava/lang/String
  • 对于数组类型来说,每一个维度使用一个前置的 [ 来表示,如int[]被表示为[I, String[][]被记录为[[Ljava/lang/String
  • 用描述符描述方法时,按照先参数列表,后返回值的顺序来描述#### 1.2 类信息、类的构造方法、类中方法信息、类变量与成员变量等信息

1.3 AccessFlag访问标志、当前类名、当前父类名、当前接口数量

访问标志信息包括:该类是类还是接口、是否被定义成public、是否是abstract、如果是类则是否被声明成final.
【深入理解JVM-1】JVM字节码文件剖析_第5张图片

  • 如下图,00 21是0020与0001的并集,表示ACC_PUBLIC与ACC_SUPER,为public并且能调用父类方法。
  • 00 03,即对应的常量池中#3,为当前类全路径名的索引
  • 00 04,即对应的常量池中#4,为当前类父类的全路径名的索引
  • 00 00,即当前接口数量为0

1.4 字段表结构

【深入理解JVM-1】JVM字节码文件剖析_第6张图片

字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,不包括方法内部声明的局部变量。

  • 00 01:表示包含一个字段
  • 00 02:字段即#2
  • 00 05:字段名字索引即a
  • 00 06:字段描述符索引即I
  • 00 00:没有属性

1.5 方法结构

【深入理解JVM-1】JVM字节码文件剖析_第7张图片
【深入理解JVM-1】JVM字节码文件剖析_第8张图片

  • 00 03:总共有3个方法
  • 00 01:访问信息为ACC_PUBLIC
  • 00 07:方法名#7
  • 00 08:方法描述符#8
  • 00 01:方法的属性结构
  • 00 09:#9即Code
  • 00 00 00 38:属性长度为56字节

Code结构:Code属性的作用是保留该方法的结构,arrtribute_length表示属性所包含的字节数,不包含arrtibute_name_index和attribute_length字段;max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度;max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入参数的局部变量。

【深入理解JVM-1】JVM字节码文件剖析_第9张图片

1.6 附加属性

其余更详细信息可通过jclasslib来查看,IDEA亦有可直接用的插件。

2. 方法表结构

方法表以指令的形式来标记字节码,每一个方法里面都会有Code属性,用来保存方法的结构,如对应字节码的信息。如上图为Code属性的主要结构:

  • arrtribute_length表示属性所包含的字节数,不包含arrtibute_name_index和attribute_length字段;
  • max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度;
  • max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入参数的局部变量。
  • code_length表示该方法所包含的字节码的字节数以及具体的字节码,具体字节码即是该方法被调用时,虚拟机所执行的字节码
  • exception_table,存放处理异常的信息,每个exception_table表项由start_pc、end_pc、handler_pc、catch_type组成

【深入理解JVM-1】JVM字节码文件剖析_第10张图片

你可能感兴趣的:(Java基础)