【深入浅出-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.1 45.3 00 03 00 2D
1.2 46.0 00 00 00 2E
1.3 47.0 00 00 00 2F
1.4 48.0 00 00 00 30
1.5 49.0 00 00 00 31
1.6 50.0 00 00 00 32
1.7 51.0 00 00 00 33
1.8 52.0 00 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_Utf8 1
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_Class 7
CONSTANT_String 8
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_NameAndType 12
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18

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 说明

字符串 类型
B byte
D double
I int
S short
V void
[ 数组
C char
F float
J long
Z boolean
L: 对象
V void方法

(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_PUBLIC 0x0001 表示public类(可以在包外访问)
ACC_FINAL 0x0010 是否为final类(final类不可继承)
ACC_SUPER 0x0020 使用增强的方法调用父类方法
ACC_INTERFACE 0x0200 是否为接口
ACC_ABSTRACT 0x0400 是否是抽象类
ACC_SYNTHETIC 0x1000 由编译器产生的类,没有源码对应
ACC_ANNOTATION 0x2000 是否是注释
ACC_ENUM 0x4000 是否是枚举

【深入浅出-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_PUBLIC 0x00001 public字段
ACC_PRIVATE 0x0002 private字段
ACC_PROTECTED 0x0004 protected字段
ACC_STATIC 0x0008 静态字段
ACC_FINAL 0x0010 是否为final字段
ACC_VOLATILE 0x0040 是否为volatile
ACC_TRANSIENT 0x0080 是否为瞬间字段,表示在持久化读写时,忽略该字段
ACC_SYNTHETIC 0x1000 由编译器产生,没有源码对应
ACC_ENUM 0x4000 是否为枚举

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

字段类型 常量池表项类型
long CONSTANT_Long
float CONSTANT_Float
double CONSTANT_Double
int,short,char,byte,boolean CONSTANT_Integer
String CONSTANT_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_PUBLIC 0x0001 public方法
ACC_PRIVATE 0x0002 private方法
ACC_PROTECTED 0x0004 protected方法
ACC_STATIC 0x0008 静态方法
ACC_FINAL 0x0010 final方法
ACC_SYNCHRONIZED 0x0020 synchroinized方法
ACC_BRIDGE 0x0040 编辑器产生的桥接方法
ACC_VARARGS 0x0080 可变参数的方法
ACC_NATIVE 0x0100 native本地方法
ACC_ABSTRACT 0x0400 抽象方法
ACC_STRICT 0x0800 浮点模式为 FP-strict
ACC_SYNTHETIC 0x1000 编译器产生的方法,没有源码

方法属性表

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

【深入浅出-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)

原文 

https://segmentfault.com/a/1190000020235132

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 【深入浅出-JVM】(69):class文件

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址