【深入浅出-JVM】(69):class文件

结构

【深入浅出-JVM】(69):class文件

结构体

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flag;
    u2 this_class;
    u2 super_class;
    u2 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];
}

例子

package com.mousycoder.mycode.thinking_in_jvm;

/**
 * @version 1.0
 * @author: mousycoder
 * @date: 2019-08-06 14:38
 */
public class SimpleUser {

    public static final int TYPE = 1;

    private int id;

    private String name;


    public int getId(){
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name;
    }
}

魔数

固定为0xCAFEBABE(James Gosling 定义),4 个字节(CAFEBABE 8 个 16 进制数,一个 16 进制数需要 4 个二进制数表示,故一共需要 32 个二进制数表示,对应 4 个字节)无符号整数,
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件

class 文件版本

小版本号由 2 个字节无符号整数,之后是大版本号,也用 2 个字节

小版本号 2 个字节为 0x0000

【深入浅出-JVM】(69):class文件

大版本号 2 个字节为 0x0034 对应十进制 52

【深入浅出-JVM】(69):class文件

版本号与平台关系

JDK 版本Class 版本号16 进制
1.145.300 03 00 2D
1.246.000 00 00 2E
1.347.000 00 00 2F
1.448.000 00 00 30
1.549.000 00 00 31
1.650.000 00 00 32
1.751.000 00 00 33
1.852.000 00 00 34

查看版本号用 javap -verbose SimpleUser
【深入浅出-JVM】(69):class文件

【深入浅出-JVM】(69):class文件

常量池

常量池的数量紧接着大版本号后面 0x0023转成 10 进制为 35,则实际常量池表项有 35-1(常量池 0为空缺项)=34个
【深入浅出-JVM】(69):class文件

常量池表项和 TAG

类型TAG
CONSTANT_Utf81
CONSTANT_Integer3
CONSTANT_Float4
CONSTANT_Long5
CONSTANT_Double6
CONSTANT_Class7
CONSTANT_String8
CONSTANT_Fieldref9
CONSTANT_Methodref10
CONSTANT_InterfaceMethodref11
CONSTANT_NameAndType12
CONSTANT_MethodHandle15
CONSTANT_MethodType16
CONSTANT_InvokeDynamic18
CONSTANT_UTF8

格式

CONSTANT_Utf8_info {
      u1 tag; // tag 规定为 1,1 个字节
      u2 length;   //字符串长度,2 个字节,最大 65535
      u1 bytes[length];  //字符串具体内容
}

【深入浅出-JVM】(69):class文件
代表整个 CONSTANT_Utf8 包括 tag ,length,bytes[length]

【深入浅出-JVM】(69):class文件

0x01转成十进制 1 ,其中 tag 为 1 代表 CONSTANT_Utf8 类型的常量

【深入浅出-JVM】(69):class文件

0x0032转成十进制50,其中 50 代表实际内容 50 个长度的字符串

【深入浅出-JVM】(69):class文件

0x4C63....3B 代表 50 个长度字符串的具体内容,从图中可知该内容为 Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; 一共 50 个长度

【深入浅出-JVM】(69):class文件

其中 0x63转成 10 进制为 99 代表字符串c

CONSTANT_Class

表示类的信息,一般只有 2 个,当前类名和object类
结构

CONSTANT_Class_info{
        u1 tag; // 固定为 7
        u2 name_index; //常量池(CONSTANT_Utf8)的索引,2个字节
}

【深入浅出-JVM】(69):class文件
类的名字在索引值为 33 的项
【深入浅出-JVM】(69):class文件
索引值 33 的项为 com/mousycoder/mycode/thinking_in_jvm/SimpleUser

CONSTANT_Integer

结构

CONSTANT_Integer{
       u1 tag;  //1个字节无符号整数
       u4 bytes;  // 4个字节无符号整数
}

【深入浅出-JVM】(69):class文件
0x03 代表 tag = 3 代表 integer ,后面 4 个字节 00 00 00 01 代表实际内容 1,其实对应代码 public static final int TYPE = 1;

CONSTANT_String

结构

CONSTANT_String_info{
     u1 tag;  //其中 tag 为 8
     u2 string_index; // 2 个字节无符号整数指向常量池的索引,表示该字符串对应的 UTF8 内容,最大 oxFF FF = 65535个常量,也是常量池的索引长度最大为 65535,和前面常量池的个数也用u2保持一致。
}

CONSTANT_Integer_info

结构

CONSTANT_Integer_info{
    u1 tag;
    u4 bytes; //4个字节的int
}
CONSTANT_Float_info

结构

CONSTANT_Float_info{
    u1 tag;
    u4 tag; // 刚好 float 4 个字节
}
CONSTANT_Long_info

结构

CONSTANT_Long_info{
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;  // 刚好一个 long 是 8 个字节
}

CONSTANT_Double_info

结构

CONSTANT_Double_info{
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;  // 刚好一个 double 是 8 个字节
}
CONSTANT_NameAndType

用于描述字段和方法,个数等于字段和方法的总和
结构

CONSTANT_NameAndType_info{
     u1 tag;  // tag 为 12
     u2 name_index; // 名字(方法、字段)所在常量池的索引
     u2 descriptor_index; // 描述信息(方法描述符、字段描述符)所在常量池的索引
}

【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
名字所在常量池的索引为 10,10 对应的字符串信息 id,描述信息所在的常量池索引为 7,7 对应的字符串信息为 I,则表明是一个名称为 id的 int表项,对应 private int id;

descriptor_index 说明

字符串类型
Bbyte
Ddouble
Iint
Sshort
Vvoid
[数组
Cchar
Ffloat
Jlong
Zboolean
L:对象
Vvoid方法

(Ljava/lang/String;)V 表示一个接受一个String参数并且返回void的方法

CONSTANT_Methodref

表示一个类的方法
结构

CONSTANT_Methodref_info {
    u1 tag;  // 固定值为 10
    u2 class_index; //指向常量池中CONSTANT_Class对象
    u2 name_and_type_index; // 指向常量池中的 CONSTANT_NameAndType对象
}

Methodref 关系图
【深入浅出-JVM】(69):class文件
解析:
【深入浅出-JVM】(69):class文件
class信息在索引 5 上找,由于这个解析器下标是从 0 开始的,所以找下标为 4 的
【深入浅出-JVM】(69):class文件
下标为 4 的 tag 为 7 代表class,名字在索引为 34 处
【深入浅出-JVM】(69):class文件
得到class的名字为 java/lang/Object;

type信息在索引 30 处找
【深入浅出-JVM】(69):class文件
30 处的 tag 为 12 代表NameAndType ,其中 name 在索引 13 处找,descriptor在索引 14 处找
【深入浅出-JVM】(69):class文件
name 为 <init>
【深入浅出-JVM】(69):class文件
descriptor 为()V
结论:代表 java/lang/Object."<init>":()V 表示Object类方法名为<init>,入参为空,返回值为空的方法,这个就是object类的构造函数,在对象实例化的时候被调用,java在编译的时候,会为每一个class 文件生成一个object类的<init>方法

【深入浅出-JVM】(69):class文件

CONSTANT_Fieldref_info

表示一个类的字段
结构

CONSTANT_Fieldref_info{
     u1 tag; // 固定值为 9
    u2 class_index;//指向常量池中CONSTANT_Class对象
    u2 name_and_type_index; // 指向常量池中的 CONSTANT_NameAndType对象
}

【深入浅出-JVM】(69):class文件
class_index 为 4,对应常量池索引为 3 的class_info
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
class_info 的索引为33 对应名字为 com/mousycoder/mycode/thinking_in_jvm/SimpleUser
name_and_type_index 对应索引值为 30 的 nameAndType
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
值为 id ,类型为I,合起来代表 com/mousycoder/mycode/thinking_in_jvm/SimpleUser.id:I

CONSTANT_InterfaceMethodref

表示一个接口方法
结构

CONSTANT_InterfaceMethodref_info{
     u1 tag;
     u2 class_index;
     u2 name_and_type_index;
}
CONSTANT_MethodType

结构

CONSTANT_MethodType_info{
    u1 tag; // tag 固定为 16
    u2 descriptor_index;
}
CONSTANT_MethodHandle

表示一个方法句柄
结构

CONSTANT_MethodHandle_info{
    u1 tag; // tag 固定为 15
    u1 reference_kind;  // 方法句柄类型
    u2 reference_index; //常量池索引
}
CONSTANT_InvokeDynamic_info

结构

CONSTANT_InvokeDynamic_info{
    u1 tag;
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}

class 访问标记

u2 access_flags; // 访问标志

类的 access flag含义

名称数值描述
ACC_PUBLIC0x0001表示public类(可以在包外访问)
ACC_FINAL0x0010是否为final类(final类不可继承)
ACC_SUPER0x0020使用增强的方法调用父类方法
ACC_INTERFACE0x0200是否为接口
ACC_ABSTRACT0x0400是否是抽象类
ACC_SYNTHETIC0x1000由编译器产生的类,没有源码对应
ACC_ANNOTATION0x2000是否是注释
ACC_ENUM0x4000是否是枚举

【深入浅出-JVM】(69):class文件
0x0021代表 0x0001 | 0x0020 等价于 ACC_PUBLIC | ACC_SUPER

当前类

u2 this_class ; // 对应常量池class_info
【深入浅出-JVM】(69):class文件

结果为 com/mousycoder/mycode/thinking_in_jvm/SimpleUser

父类

u2 super_class; // 对应常量池 class_info , java 只有一个父类,所以这里只保存一个
【深入浅出-JVM】(69):class文件
结果为 java/lang/Object

接口个数

u2 interfaces_count; //接口个数

接口列表

u2 interfaces[interfaces_count]; //具体接口列表

字段个数

u2 fields_count; //字段个数
【深入浅出-JVM】(69):class文件
个数为 3 个,其实对应 int TYPE,int id,String name

字段

结构

field_info {
     u2 access_flags; //字段访问标志,见 access flag
     u2 name_index; //字段名称,对应常量池索引
     u2 descriptor_index; //字段类型,对应常量池索引
     u2 attributes_count; 
     attribute_info attibutes[attributes_count];
}

attribute_info{
    u2 attribute_name_index; // 属性名称,指向常量池CONSTANT_Utf8,并且这个值为ConstantValue
    u4 atrribute_length;// 属性剩余长度,对于常量而言,这个值恒为 2
    u2 constantvalue_index; // 类型,对应常量池的索引
}

字段的 access flag 描述

名称数值描述
ACC_PUBLIC0x00001public字段
ACC_PRIVATE0x0002private字段
ACC_PROTECTED0x0004protected字段
ACC_STATIC0x0008静态字段
ACC_FINAL0x0010是否为final字段
ACC_VOLATILE0x0040是否为volatile
ACC_TRANSIENT0x0080是否为瞬间字段,表示在持久化读写时,忽略该字段
ACC_SYNTHETIC0x1000由编译器产生,没有源码对应
ACC_ENUM0x4000是否为枚举

常量数据类型与常量池类型关系

字段类型常量池表项类型
longCONSTANT_Long
floatCONSTANT_Float
doubleCONSTANT_Double
int,short,char,byte,booleanCONSTANT_Integer
StringCONSTANT_String

【深入浅出-JVM】(69):class文件
0x0019 对应,ACC_PUBLIC_ACC_STATIC|ACC_FINAL
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
0x0006代表常量池索引 5,值为TYPE

【深入浅出-JVM】(69):class文件

descriptor_index 为 7 代表 I

【深入浅出-JVM】(69):class文件
代表只有 1 个属性值

【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
代表该字段的属性为ConstantValue
【深入浅出-JVM】(69):class文件
代表剩余字段长度为 2,就是后面 2 个字节代表属性的所有内容 0x0009
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
代表为一个CONSTANT_Integer 并且值为 1
总结代表 public static final int TYPE = 1

【深入浅出-JVM】(69):class文件

方法个数

u2 methods_count ; // 方法个数
【深入浅出-JVM】(69):class文件
代表有 5 个方法

方法信息

结构

method_info {
    u2 access_flags;//方法访问标记
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attributes_info attributes[attributes_count];
}

attribute_info{
    u2 attribute_name_index; // 名称
    u4 attribute_length; // 剩余长度
    u2 max_stack;// 操作数栈
    u2 max_locals;// 局部变量最大值
    u4 code_length;//字节码长度 
    code[code_length];//字节码具体内容
    u2 exception_table_length;
    {
        u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    }
    u2 exception_table_length;// 异常表长度
    u2 attributes_count;//属性长度
    {
        u2 attribute_name_index; //名称
        u4 attribute_length; //剩余长度
        u2 line_number_table_length; 具体内容
        {
            u2 start_pc; //字节码偏移量
            u2 line_number;//字节码行号
        }
    }
   {
        u2 attribute_name_index; //对应常量表 LocalVariableTable 
        u4 attribute_length;//
        u2 local_variable_table_length;
        {
            u2 start_pc; // 局部变量的开始位置(start_pc),结束位置(start_pc+length)
            u2 length;//长度
            u2 name_index; //常量池中索引位置
            u2 descriptor_index; //描述符常量池中的索引
            u2 index; //栈帧的局部变量表的槽位
        } local_variable_table[local_variable_table_length];
   }

}

方法访问标记取值

标记名称作用
ACC_PUBLIC0x0001public方法
ACC_PRIVATE0x0002private方法
ACC_PROTECTED0x0004protected方法
ACC_STATIC0x0008静态方法
ACC_FINAL0x0010final方法
ACC_SYNCHRONIZED0x0020synchroinized方法
ACC_BRIDGE0x0040编辑器产生的桥接方法
ACC_VARARGS0x0080可变参数的方法
ACC_NATIVE0x0100native本地方法
ACC_ABSTRACT0x0400抽象方法
ACC_STRICT0x0800浮点模式为 FP-strict
ACC_SYNTHETIC0x1000编译器产生的方法,没有源码

方法属性表

属性作用
ConstantValue字段常量
Code方法的字节码
StackMapTableCode 属性的描述,用于字节码变量类型验证
Exceptions方法的异常信息
SourceFile类文件的属性
LineNumberTableCode属性的描述属性,描述行号和字节码的对应关系
LocalVariableTableCode 属性的描述属性,描述函数局部变量
BootstrapMethods类文件的描述属性,存放类的引导方法,用于 invokeDynamic
StackMapTableCode属性的描述属性,用于字节码类型校验

【深入浅出-JVM】(69):class文件
access_flags 为 0x0001 代表 public 方法

【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
name_index 为 26 代表setName
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
descriptor_index为 27 代表 (Ljava/lang/String;)V 代表方法的入参是String,返回值为Void

【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
attrubute_name_index 为 15 ,找到常量池索引为 14 的,值为Code 表示方法的字节码
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
attribute_length 为 62 代表 剩余长度为 62,一共 62 个字符

【深入浅出-JVM】(69):class文件
max_stack 为 2 代表操作数栈最大深度
【深入浅出-JVM】(69):class文件
max_locals 代表局部变量表最大值为 2
【深入浅出-JVM】(69):class文件
code_length 为 6,代表数组长度为 6,struct code 代表具体字节码内容,可以看到是 aload_0 ,aload_1,putfield com/mousycoder/mycode/thinking_in_jvm/SimpleUser.nameLjava/lang/String;,Return

【深入浅出-JVM】(69):class文件
exception_table_length 为 0 代表没有异常表
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
attribute_name_index 为 16,代表常量池中索引获得值为LineNumberTable,剩余长度为 10,line_number_table_length为 2,代表 2 个line_number_table ,start_pc 为字节码偏移量,line_number为行号 30
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
【深入浅出-JVM】(69):class文件
setName方法里变量名字有 2 个,一个是 this,一个是name

属性

【深入浅出-JVM】(69):class文件
结构

attribute_info {
    u2 attribute_name_index; //属性名
    u4 attribute_length; 属性长度
    u2 sourcefile_index; //属性文件  
}

总结

警告: 二进制文件SimpleUser包含com.mousycoder.mycode.thinking_in_jvm.SimpleUser
Classfile /Users/mousycoder/My/code/mycode/target/classes/com/mousycoder/mycode/thinking_in_jvm/SimpleUser.class
  Last modified 2019-8-23; size 818 bytes
  MD5 checksum 20de23f9dc93bcf724f584ead999f846
  Compiled from "SimpleUser.java"
public class com.mousycoder.mycode.thinking_in_jvm.SimpleUser
  SourceFile: "SimpleUser.java"
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref #5.#30 // java/lang/Object."<init>":()V
   #2 = Fieldref #4.#31 // com/mousycoder/mycode/thinking_in_jvm/SimpleUser.id:I
   #3 = Fieldref #4.#32 // com/mousycoder/mycode/thinking_in_jvm/SimpleUser.name:Ljava/lang/String;
   #4 = Class #33 // com/mousycoder/mycode/thinking_in_jvm/SimpleUser
   #5 = Class #34 // java/lang/Object
   #6 = Utf8 TYPE
   #7 = Utf8 I
   #8 = Utf8 ConstantValue
   #9 = Integer 1
  #10 = Utf8 id
  #11 = Utf8 name
  #12 = Utf8 Ljava/lang/String;
  #13 = Utf8 <init>
  #14 = Utf8 ()V
  #15 = Utf8 Code
  #16 = Utf8 LineNumberTable
  #17 = Utf8 LocalVariableTable
  #18 = Utf8 this
  #19 = Utf8 Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser;
  #20 = Utf8 getId
  #21 = Utf8 ()I
  #22 = Utf8 setId
  #23 = Utf8 (I)V
  #24 = Utf8 getName
  #25 = Utf8 ()Ljava/lang/String;
  #26 = Utf8 setName
  #27 = Utf8 (Ljava/lang/String;)V
  #28 = Utf8 SourceFile
  #29 = Utf8 SimpleUser.java
  #30 = NameAndType #13:#14 // "<init>":()V
  #31 = NameAndType #10:#7 // id:I
  #32 = NameAndType #11:#12 // name:Ljava/lang/String;
  #33 = Utf8 com/mousycoder/mycode/thinking_in_jvm/SimpleUser
  #34 = Utf8 java/lang/Object
{
  public static final int TYPE;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

  public com.mousycoder.mycode.thinking_in_jvm.SimpleUser();
    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 8: 0
      LocalVariableTable:
        Start Length Slot Name Signature
            0 5 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser;

  public int getId();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield #2 // Field id:I
         4: ireturn       
      LineNumberTable:
        line 18: 0
      LocalVariableTable:
        Start Length Slot Name Signature
            0 5 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser;

  public void setId(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 id:I
         5: return        
      LineNumberTable:
        line 22: 0
        line 23: 5
      LocalVariableTable:
        Start Length Slot Name Signature
            0 6 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser;
            0 6 1 id I

  public java.lang.String getName();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield #3 // Field name:Ljava/lang/String;
         4: areturn       
      LineNumberTable:
        line 26: 0
      LocalVariableTable:
        Start Length Slot Name Signature
            0 5 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser;

  public void setName(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: aload_1       
         2: putfield #3 // Field name:Ljava/lang/String;
         5: return        
      LineNumberTable:
        line 30: 0
        line 31: 5
      LocalVariableTable:
        Start Length Slot Name Signature
            0 6 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser;
            0 6 1 name Ljava/lang/String;
}

一共 818 个bytes(818 个 xx)

相关推荐