JVM class文件分析

249 阅读4分钟

JVM Class文件详解

.java 文件

public class JVMClassInfo {

    public void sayHello(){
        System.out.println("hello,world!");
    }
}

执行Javap -verbose /你的文件路径/JVMClassInfo.class

public class com.jvm.JVMClassInfo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#17         // java/lang/Object."<init>":()V
   #2 = Fieldref           #18.#19        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #20            // hello,world!
   #4 = Methodref          #21.#22        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #23            // com/jvm/JVMClassInfo
   #6 = Class              #24            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/jvm/JVMClassInfo;
  #14 = Utf8               sayHello
  #15 = Utf8               SourceFile
  #16 = Utf8               JVMClassInfo.java
  #17 = NameAndType        #7:#8          // "<init>":()V
  #18 = Class              #25            // java/lang/System
  #19 = NameAndType        #26:#27        // out:Ljava/io/PrintStream;
  #20 = Utf8               hello,world!
  #21 = Class              #28            // java/io/PrintStream
  #22 = NameAndType        #29:#30        // println:(Ljava/lang/String;)V
  #23 = Utf8               com/jvm/JVMClassInfo
  #24 = Utf8               java/lang/Object
  #25 = Utf8               java/lang/System
  #26 = Utf8               out
  #27 = Utf8               Ljava/io/PrintStream;
  #28 = Utf8               java/io/PrintStream
  #29 = Utf8               println
  #30 = Utf8               (Ljava/lang/String;)V
{
  public com.jvm.JVMClassInfo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/jvm/JVMClassInfo;

  public void sayHello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello,world!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/jvm/JVMClassInfo;
}

主要是 jdk的信息、常量池、以及class文件的字节码样式。

idea 可以安装一个 jclasslib Bytecode viewer 插件来查看字节码信息。

用 Javap 看字节码清楚点,用插件看整个class文件结构更清晰。上面的图中少了一个class信息:MagicNumber。可以使用十六进制的工具打开class文件看class文件的十六进制。

一个class文件分为:MagicNumber,Version,Constant_pool,Access_flag,This_class,Super_class,Interfaces,Fields,Methods ,Attributes十个部分。

  • MagicNumber:class文件开头用4个字节长度表示的值为CA FE BA BE ,Java虚拟机用此来验证一个文件是否为class文件,如果不是,Java虚拟机将拒绝加载此class

  • Version:分为Minor Version和Major Version,表示当前的jdk版本号,次版本号和主版本号。跟在MagicNumber后面,00 00:次版本号,对应的十进制是 0 ;00 34 : 主版本号,对应的十进制是 52 。在我们当前分析的环境中代表的是jdk1.8。

  • Constant_pool:常量池,00 1F 两个字节表示常量池的数量,由于常量池从1开始算,所以count为30(31-1)。常量池里面放的是字面常量和符号引用,字面常量主要包含文本串以及被声明为final的常量等;符号引用包含类和接口的全局限定名、字段的名称和描述、方法的名称和描述符。

    常量的结构以常量的类型开始,接着是数据。类似于下面的结构:

    cp_info{
    	u1 tag;
      u1 info[];
    }
    

    tag对应的是常量对应的类型。

    Constant Type Tag
    CONSTANT_Utf8_info 1
    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_InvokeDynamic_info 18

    CONSTANT_xxx结构

    CONSTANT_Methodref_info{
    	u1 tag;
    	u2 class_index;
    	u2 name_and_type_index;
    }
    CONSTANT_Fieldref_info{
      u1 tag;
      u2 class_index;
      u2 name_and_type_index;
    }
    CONSTANT_Class_info{
      u1 tag;
      u2 name_index;
    }
    CONSTANT_Utf8_info{
      u1 tag;
      u2 length;
      u1 bytes[length];
    }
    CONSTANT_NameAndType_info{
      u1 tag;
      u2 name_indx;
      u2 descriptor_index;
    }
    

    分析下常量池数量后面的常量池信息。

    0A —>10 表示的是 CONSTANT_Methodref_info ,所以有5个字节 0A 00 06 00 11 来表示,00 06代表的是 class_index , 即 方法所属class信息的索引在常量池中的第6个,00 11 代表的是 name_and_type_index ,即方法的描述符位于常量池中的第17个,通过上文中提到的插件可以轻易的看出表示的信息是什么。

后面的信息就依据此方法分析就可以了,这里就不多赘述了。

  • Access_flag:类或接口修饰符,访问权限。

    ACC_PUBLIC/ACC_FINAL/ACC_SUPER/ACC_INTERFACE/ACC_ABSTRACT/ACC_SYNTHETIC/ACC_ANNOTATION/ACC_ENUM/...

    常量池之后是access_flag,两个字节表示。

图中光标的位置为access_flag的起始位置,00 21来表示。 0x0021 = 0x0001 | 0x0020,表示当前class的access_flag是ACC_PUBLIC|ACC_SUPER。

  • This_class : 保存了当前类的全局限定名在常量池的索引,两个字节表示,00 05 这里指向第5个常量,即

  • Super_class: 保存了当前类的父类的全局限定名在常量池里的索引,两个字节表示,00 06 这里指向第6个常量,即

  • Interfaces: 保存了当前类实现的接口列表,包含两个部分:interfaces_count、interfaces[interfaces_count]

  • fields: 保存了当前类的成员列表,包含两个部分:fields_count、fields[fields_count]

    field_info{
    	u2 access_flags;
    	u2 name_index;
    	u2 descriptor_index;
    	u2 attributes_count;
    	attribute_info attributes[attributes_count];
    }
    
  • methods: 保存了当前类的方法列表,包含两个部分:methods_count、methods[methods_count]

    method_info{
    	u2 access_flags; // 00 01 ACC_PUBLIC
    	u2 name_index; // 00 07  <init>
    	u2 descriptor_index; //00 08 V()
    	u2 attributes_count; //00 01 
    	attribute info attributes[attributes_count];
    }
    

    因为当前类中既没有interfaces,也没有fields。所以 method 从 00 02开始,表示有两个方法。

  • attributes: class文件的附加属性。包括 Code、LineNumberTable、LocalVariableTable、SourceFile等

Thanks

www.cnblogs.com/avivahe/p/5…