class文件详解


class文件结构

使用sublime text等可以查看16进制文件的工具,打开任意一个.class文件,可以看到如下内容:

其中大部分内容都是看不懂的,唯一能看懂的就是最开始的8个字母cafe babe,这个叫做“魔数”。

魔数是用来标记这个文件是什么类型的文件的。

很多类型的文件都使用了魔数,例如:

魔数之后才是class文件真正的内容。整个class文件结构大致如下:

含义解析:

结构 含义
CA FE BA BE 魔数(咖啡宝贝),标记该文件是一个class文件
副版本号 编译的JDK副版本号,比如1.8.0,副版本就是0
主版本号 编译的JDK主版本号,比如1.8.0,主版本号是1.8
常量计数器 该类中的常量数量
常量池数据 常量的数据,常量的数量由“常量计数器”决定
访问标志 类的访问权限,如public
类索引 本类的引用信息
父类索引 父类的引用信息
接口计数器 该类中接口数量
接口信息数据 接口数据
字段计数器 该类中的字段数量
字段信息数据 字段详细信息
方法计数器 该类中的方法数量
方法信息数据 方法信息
属性计数器 属性数量
属性信息数据 属性信息,是对前面的数据的一个补充。例如方法信息区中无法保存方法体内容,则放在属性信息数据中保存

工欲善其事必先利其器,为了更好的学习class文件,推荐安装IDEA的class解析插件,叫jclasslib Bytecode Viewer,安装好后,选中一个类文件,点击view->show bytecode with jclasslib 即可查看class文件信息

如果发现提示class root not found,只需要执行一下main方法,让代码编译一下即可。

常量池结构

类中常量的数量是不一定的,因此有一个常量计数值(constant_pool_count)来表示常量的数量

这个数值有点特殊,它是从1开始计数,假设这个值是20,那么实际常量的数量其实是19

常量池中主要有两大类常量:

  1. 字面量:Java语言层面的常量概念,比如final修饰的常量,字符串等
  2. 符号引用:类、接口、字段、方法引用等

常量的数据结构类型主要有:

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8字符串
CONSTANT_Integer_info 3 int常量
CONSTANT_Float_info 4 float常量
CONSTANT_Long_info 5 long常量
CONSTANT_Double_info 6 double常量
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 动态方法调用点

以上类型的常量,都有各自不同的数据结构,使用标志tag来标记是哪一种常量,举几个例子:

CONSTANT_Integer_info{
	u1 tag=3;
    u4 bytes;
}

该结构中:

  • u1中的1,表示无符号的1个字节,u4则表示4个字节
  • tag=3,对照上面的表格,说明这是int常量
  • bytes则是int的值

类似,CONSTANT_Float_info有着和CONSTANT_Integer_info几乎一样的结构

CONSTANT_Float_info{
	u1 tag=4;
    u4 bytes;
}

double和long因为占用8个字节,因此和int、float有些许不同,例如:

CONSTANT_Long_info{
    u1 tag=6;
    u4 high_bytes;
    u4 low_bytes;
}

long和double的数据有高位字节和低位字节

CONSTANT_String_info的结构也不一样:

CONSTANT_String_info{
    u1 tag=8;
    u2 string_index;
}

String类型常量结构并不直接存储字符串,而是保存了一个索引,指向CONSTANT_Utf8_info:

CONSTANT_Utf8_info{
    u1 tag=1;
    u2 length;
    u1 bytes[length]
}

​ 其中length表示字符串的字节数,bytes[length]则是字符串的字节数组

类似的,class类型的常量也是保存了一个引用:

CONSTANT_Class_info{
    u1 tag=7;
    u2 name_index;
}

name_index指向了一个字符串CONSTANT_Utf8_info,该字符串保存了类的全限定名,即含包名的完整类名,如java/lang/Object

与之类似的,类中的方法和字段也使用了这种方法来表示

那么哪些字面量类型的值会进入常量池呢?

  1. final修饰的基本数据类型会进入常量池
  2. 非final类型的值,只有double,float,long类型的才会进入常量池
  3. 常量池中包含的字符串类型字面量会进入常量池(双引号的字符串值)

可以使用jclasslib Bytecode Viewer查看常量池情况,例如下方的类,可以和上面提到的三种情况一一对照

访问标记

访问标记比较简单,有以下几种:

名称 含义 名称 含义
ACC_PUBLIC 是否为public ACC_ABSTRACT 是否抽象
ACC_FINAL 是否为final ACC_SYNTHETIC 不是用户代码生成
ACC_SUPER 始终为true,没啥用 ACC_ANNOTATION 是否是注解
ACC_INTERFACE 是否是接口 ACC_ENUM 是否是枚举

类索引

类索引包含本类索引,父类索引,接口索引,它们都是一种符号引用,指向了常量池中对应的CONSTANT_Class_info

字段

字段包含几个部分:

  1. 访问标记,如public,private
  2. 字段名
  3. 描述符

字段访问标记:

标记 含义 标记 含义
ACC_PUBLIC 是否public ACC_VOLATILE 是否volatile
ACC_PRIVATE 是否private ACC_TRANSIENT 是否transient
ACC_PROTECTED 是否protected ACC_SYNTHETIC 是否由编译器自动产生
ACC_STATIC 是否static ACC_ENUM 是否是枚举
ACC_FINAL 是否final

字段名:一种符号引用,指向常量池

描述符,用于表示字段的类型:

描述符标志 含义 描述符标志 含义
B byte J long
C char S short
D double Z boolean
F float V void
I int L 对象类型,如Ljava/lang/Object

数组类型的,则使用符号[表示,n维数组就有n个[,例如:

  • String[]的描述符为[Ljava/lang/Object

  • float[][]的描述符为[[F

方法

方法和字段的结构一致,也是包含访问标记、名称、描述符

方法访问标记

标记 含义 标记 含义
ACC_PUBLIC 是否public ACC_BRIDGE 是否由编译器产生的桥接方法
ACC_PRIVATE 是否private ACC_VARARGS 是否接受不定参数
ACC_PROTECTED 是否protected ACC_NATIVE 是否为native
ACC_STATIC 是否static ACC_ABSTRACT 是否abstract
ACC_FINAL 是否final ACC_STRICTFP 是否stricfp
ACC_SYNCHRONIZED 是否synchronized ACC_SYNTHETIC 是否由编译器自动产生

方法名:一个符号引用,指向常量池字符串

描述符:方法的描述符,基本格式为(方法参数1;方法参数2)返回值,例如以下方法:

private int myMethod(String[] args, int args2){
    return 0;
}

对应的描述符为([java/lang/String;I)I

奇怪的是,方法区里只有方法的签名,却没有方法的具体代码。

其实方法的代码,被编译成字节码之后保存在属性信息数据中了

属性

字段、方法等数据,都可以携带自己的属性,例如方法的源码,则是编译成字节码保存在名为Code的属性中

文章作者: 周君
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 周君 !
评论