【深入浅出-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 个字节)无符号整数,

class 文件版本

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

小版本号 2 个字节为 0x0000

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

版本号与平台关系
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

常量池

常量池的数量紧接着大版本号后面 0x0023转成 10 进制为 35,则实际常量池表项有 35-1(常量池 0为空缺项)=34个

常量池表项和 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];  //字符串具体内容
}

代表整个 CONSTANT_Utf8 包括 tag ,length,bytes[length]

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

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

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

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

CONSTANT_Class

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

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

类的名字在索引值为 33 的项索引值 33 的项为 com/mousycoder/mycode/thinkinginjvm/SimpleUser

CONSTANT_Integer

结构

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

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保持一致。
}

CONSTANTIntegerinfo

结构

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

结构

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

结构

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

CONSTANTDoubleinfo

结构

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; // 描述信息(方法描述符、字段描述符)所在常量池的索引
}

名字所在常量池的索引为 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 关系图解析:class信息在索引 5 上找,由于这个解析器下标是从 0 开始的,所以找下标为 4 的下标为 4 的 tag 为 7 代表class,名字在索引为 34 处得到class的名字为 java/lang/Object;

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

CONSTANTFieldrefinfo

表示一个类的字段结构

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

classindex 为 4,对应常量池索引为 3 的classinfoclassinfo 的索引为33 对应名字为 com/mousycoder/mycode/thinkingin_jvm/SimpleUsernameandtype_index 对应索引值为 30 的 nameAndType值为 id ,类型为I,合起来代表 com/mousycoder/mycode/thinkinginjvm/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; //常量池索引
}
CONSTANTInvokeDynamicinfo

结构

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是否是枚举

0x0021代表 0x0001 | 0x0020 等价于 ACCPUBLIC | ACCSUPER

当前类

u2 thisclass ; // 对应常量池classinfo

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

父类

u2 superclass; // 对应常量池 classinfo , java 只有一个父类,所以这里只保存一个结果为 java/lang/Object

接口个数

u2 interfaces_count; //接口个数

接口列表

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

字段个数

u2 fields_count; //字段个数个数为 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

0x0019 对应,ACCPUBLICACCSTATIC|ACCFINAL0x0006代表常量池索引 5,值为TYPE

descriptor_index 为 7 代表 I

代表只有 1 个属性值

代表该字段的属性为ConstantValue代表剩余字段长度为 2,就是后面 2 个字节代表属性的所有内容 0x0009代表为一个CONSTANT_Integer 并且值为 1总结代表 public static final int TYPE = 1

方法个数

u2 methods_count ; // 方法个数代表有 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属性的描述属性,用于字节码类型校验

access_flags 为 0x0001 代表 public 方法

name_index 为 26 代表setNamedescriptor_index为 27 代表 (Ljava/lang/String;)V 代表方法的入参是String,返回值为Void

attrubutenameindex 为 15 ,找到常量池索引为 14 的,值为Code 表示方法的字节码attribute_length 为 62 代表 剩余长度为 62,一共 62 个字符

max_stack 为 2 代表操作数栈最大深度max_locals 代表局部变量表最大值为 2codelength 为 6,代表数组长度为 6,struct code 代表具体字节码内容,可以看到是 aload0 ,aload1,putfield com/mousycoder/mycode/thinkingin_jvm/SimpleUser.nameLjava/lang/String;,Return

exceptiontablelength 为 0 代表没有异常表attributenameindex 为 16,代表常量池中索引获得值为LineNumberTable,剩余长度为 10,linenumbertablelength为 2,代表 2 个linenumbertable ,startpc 为字节码偏移量,line_number为行号 30setName方法里变量名字有 2 个,一个是 this,一个是name

属性

结构

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值