粗谈Java虚拟机2_Class文件分析

1. 前言

class文件作为 JVM 的可执行文件,在可读性方面比 C语言 等直接编译成平台可执行文件的语言强太多,反编译class文件往往能够得到不错的效果。而一个类无论代码的多少,在结构上都大同小异。在源码级,类的结构由上而下大致为:当前类的包名路径、引用类的包名路径、当前类的信息(类名、父类、接口)、变量、方法、内部类/内部接口/枚举/注解等。Javac编译器也按照该顺序来编译源码。
在这里插入图片描述

同时将代码中所使用到的引用类包名路径、变量命名和赋予的值,方法名等信息,均都存储在常量池中。当需要使用时,以索引下标的方式指向常量池中的位置。分析class文件没有任何的技术难度,明白各个数据项的结构,分析起来将会很轻松。


本文以下列源码为例,带大家一起来分析 class 文件 :

public class Person implements Serializable{
	
	private static final long serialVersionUID = 1L;
	
	private String name;
	
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	
}

编译为 class 文件后:
在这里插入图片描述

2. 魔数和Class编译版本
  • 魔数,CA FE BA BE

    固定值,标识文件是否是 class 文件。

  • 版本,00 00 00 34

    编译JDK版本,minor_version 和 major_version 一起确定class文件格式的版本,前2个字节小版本,后两个字节大版本。

在这里插入图片描述

3. 常量池

常量池中存储 class文件 中的各种符号引用和字面量,是Class文件的资源仓库,有多全面呢?源码一眼望去,除了各种的关键字和方法体中的字节码指令,其余全部存储在常量池当中,当需要使用时,通过索引下标的方式来引用。常量类型:

Tag类型描述
ox1CONSTANT_Uft8Utf-8编码的字符串
ox3CONSTANT_Integer整型字面量
ox4CONSTANT_Float浮点型字面量
ox5CONSTANT_Long长整型字面量
ox6CONSTANT_Double双精度字面量
ox7CONSTANT_Class类或接口的符号引用
ox8CONSTANT_String字符串类型字面量
ox9CONSTANT_Fieldref字段的符号应用
ox10CONSTANT_Methodref类中方法的符号引用
ox11CONSTANT_InterfaceMathodref接口中方法的符号引用
ox12CONSTANT_NameAndType字段或方法的部分符号引用
ox15CONSTANT_MethodHand表示方法句柄
ox16CONSTANT_MethType标识方法类型
ox18CONSTANT_InvokedDaynamic表示一个动态方法调用点

在这里插入图片描述前两个字节 0x001F 为常量池大小,共有 31 项常量。第 1 项常量 tag 为 07 查上图得知为 class_info 类型,class_info 结构如下:

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

长度为 u2 大小的 name_index 必须指向一个 CONSTANT_Utf8_info 类型的常量。此处 02 指向第二项常量。第 2 项常量 tag 为 01 查表得知为 CONSTANT_Utf8_info 类型常量,结构如下:

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

对应的字节为:01 00 10 + 后面依次16个字节(linked/TestClass ) 点击查看更多类型,使用 JDK 自带的 javap 工具,可查看常量池信息:
在这里插入图片描述

4. 类信息
4.1 访问标志

常量池结束后,紧接着就是类或接口的信息。前两个字节代表类或接口的访问标志,被哪些关键字修饰:

标志名称标志值描述
ACC_PUBLIC0x0001是否声明为public
ACC_FINAL0x0010是否声明为final
ACC_SUPER0x0020是否使用1.2版本编译器(支持使用新的invokespecial指令)
ACC_INTERFACE0x0200接口类型
ACC_ABSTRACT0x0400抽象类_类型
ACC_SYNTHETIC0x0100编译器合成,并非用户代码编写
ACC_ANNOTATION0x2000注解类型
ACC_ENUM0x4000枚举类型

通过ACC_INTERFACE 标志来区分 Class 是类还是接口。如果设置 ACC_INTERFACE ,则 ACC_ABSTRACT、ACC_SUPER、ACC_ENUM 不能设置。

acc_super标志是否使用invokespecial指令,在调用构造方法或使用super.xxx()显示调用父类方法时,会使用该指令。1.2之前invokespecial对方法的调用都是静态绑定的。java1.2 的时候增加了动态绑定的功能。

当前类被声明为 public类型,使用1.2以上编译器。ACC_PUBLICACC_SUPER为真,0x0001 | 0x0020 = 0x0021。

在这里插入图片描述

4.2 类索引、父类索引和接口索引

接下来分别是类、父类、接口信息,直接引用常量池中的符号引用,类的全限命名。结构上类信息、父类信息都用一个u2大小字节引用常量池中的索引。由于接口是可以多实现的,所以先用u2字节大小记录实现了多少个接口,再依次在后面数几个u2大小字节。

在这里插入图片描述

类型索引常量池
类索引0x0001linked/Person
父类索引0x0003java/lang/Object

Person 类实现了 Serializable 接口,所以 interface_count 等于 1,紧接着 0005 代表引用常量池索引为 5 的常量。得知为 java/io/Serializable

5. 字段表

字段表值包含成员变量,常量会存在常量池当中。

字段表结构:

类型名称数量说明
u2access_flags1修饰符/访问标志
u2name_index1变量命名
u2descriptor1数据类型
u2attributes_count1属性表长度
attribute_infoattributesattributes_count属性表

修饰符/访问标志:

标志名称标志值含义标志名称标志值含义
ACC_PUBLIC0X0001字段是否publicACC_VOLATILE0x0040字段是否volatile
ACC_PRIVATE0x0002字段是否privateACC_TRANSIENT0x0080字段是否transient
ACC_PROTECTED0x0004字段是否protectedACC_SYNTHETIC0x1000字段是否由编译器自动产生
ACC_STATIC0x0008字段是否staticACC_ENUM0x4000字段是否enum
ACC_FINAL0x0010字段是否final

字段类型:
除开 八大基本类型 和 引用类型,还有数组类型,方法还有 Void 类型。

描述符类型描述符类型
BbyteJlong
CcharSshort
DdoubleZboolean
FfloatVvoid
IintL引用类型
[数组纬度

在这里插入图片描述
0x0002 字段表数量,只有二个字段

第一个字段:

  • 访问标志 0x001A :private static final 0x02 | 0x08 | 0x10 等于 0x1A
  • 变量名 0x0007 :serialVersionUID
  • 变量类型 0x0008 :对应常量池中的 J,转换为 long 类型。
  • 一个属性 0x0001 :详见* ConstantValue属性分析

第二个字段:

  • 访问标志 0x0002:private
  • 变量名 0x000C:name
  • 变量类型 0x000D: Ljava/lang/String;
  • 没有属性 0x0000;
6. 方法表

方法的结构有各种修饰符、返回值类型、方法名、参数列表、再就是方法体内的代码。结构如下:

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

[外链图片转存失败(img-TR4VJuHA-1564474493355)(E:/study/文章做图/JVM/微信截图_20190729104127.png)]

  • 0x0003 3个方法

第一个方法:默认构造函数

  • 0x0001 访问标志:public
  • 0x000E 方法名:init
  • 0x000F 返回值类型:V()
  • 0x0001 一个属性:00 10 在常量池中Code属性,详见 Code属性分析
7. 属性表

字段和方法表中都可以携带自己的属性,JVM 的属性表类型较多,本篇文章只泛泛而谈出现的两种(更多属性):

7.1 ConstantValue属性

同时被 staticfinal 关键字修饰的字段,会以 ConstantValue 属性表的结构存储在Class文件种,也就是平时大家说的常量。并且做为类或接口的一部分,存储在运行时常量池当中。格式如下:

ConstantValue_attribute {
    u2 attribute_name_index; //属性表名称位于常量池中的位置
    u4 attribute_length;    //属性表长度大小
    u2 constantvalue_index; //常量值位于常量池中的位置
}

接着上面的Class文件分析:

  • 00 09 对应常量池中的 ConstantValue
  • 00 00 00 02 :两个字节长度
  • 00 0A :1L
7.2 Code属性

方法体中的代码,经Javac编译器处理后存放在方法的Code属性表中。抽象方法和接口不存在Code属性。Code属性结构如下∶

Code_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 u2 max_stack;
 u2 max_locals;
 u4 code_length;
 u1 code[code_length];
 u2 exception_table_length;
 { 
    u2 start_pc;
    u2 end_pc;
    u2 handler_pc;
    u2 catch_type;
 } 
 exception_table[exception_table_length];
 u2 attributes_count;
 attribute_info attributes[attributes_count];
}

对应的字节码为:
在这里插入图片描述
使用 javap 命令得出的:在这里插入图片描述

  • 0x0010,Code
  • 0x0001,操作数栈大小
  • 0x0001,局部变量表大小
  • 0x2A,从表中查询对应的指令为aload_0,该指令将局部变量表的第一个(索引为0)变量推送至栈顶。
  • 0xB7,从表中查询对应的指令为invokespecial,方法调用指令,必须声明一个方法命名,可以是构造方法、父类的构造方法、私有方法。后面u2大小,指向常量池中一个CONSTANT_Methodref_info类型的项。
  • 0x0011,invokespecial 所执行的方法,对应常量池中的<init>方法的符号引用。
  • 0x00B1,从表中查询对应的指令为return,当前方法返回 void。
  • 0x0000,不存在异常表

code_legth 虽然是一个u4大小的值,理论上可以存储0xffffffff(40多亿)大小的字节码指令,但是虚拟机规范中明确规定限制了一个方法不超过65535条字节码指令,实际只使用了u2(0xffff)大小。

点击查看字节码指令

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值