Class文件结构 学习笔记

本文详细解析了Java Class文件的结构,包括字节码、常量池、访问标志、字段和方法信息等内容,揭示了Java类如何被编译并存储为Class文件。

参考《深入理解Java虚拟机》

Class文件结构 学习笔记

一、 class文件产生

.java文件经javac编译生成.class文件,才能被虚拟机解析运行,class文件是一组以8位字节为基础单位的二进制流,所以称为字节码。

二、 class文件结构

1. 整体结构

class文件采用类似C语言的结构体格式存储数据。只有两种类型:无符号整数、表。无符号整数u1、u2、u4、u8等通常用来描述索引引用或者数字的字面量。表则由多个无符号数或者其它的表组成,用以描述一种数据结构,通常以_info结尾。当描述的同一类型数据个数不定时,要提前使用一个计数器表示这个数量,因为class文件没有分隔符,所以各部长度要确定。这个class文件相当于一张表,由以下数据项构成:
在这里插入图片描述
假如现在有这个class文件:
在这里插入图片描述
使用javac编译成.class文件,使用16进制阅读器打开是这个样子的
在这里插入图片描述
在这里插入图片描述
使用jdk自带的javap打开是这个样子的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. magic、minor_version、major_version

  1. magic,前四个字节是魔数,值为0xCAFEBABE,用以标识这个文件是class文件。在这里插入图片描述
  2. minor_version,接下来两个字节是小版本号,表示的是大的java版本(比如java10、java8等)下发行的小版本(比如10.0.2等),我的jdk是10.0.2的,小版本号是0,大版本是54.
    在这里插入图片描述
  3. major_version,再接下来两个字节是大版本号,表示当前的jdk版本,比如我的是Java10,major_version是54。
    在这里插入图片描述

3. constant_pool_count、constant_pool

在这里插入图片描述
接下来两个自己是常量池大小,表示常量池中常量个数。比如上面的实例中以一共有26个常量,对应在十六进制文件中是001B,也就是27,所以常量个数是constant_pool_count-1,常量池就是class文件的仓库,class文件的其它表结构几乎都是通过指向常量池的索引来描述的。
通过javap反编译的常量池可以看到constant_pool其实是由很多种类的,比如class、string、methodref、fieldref、utf8、nameAndType等等。常量池中共有14种常量项,它们的表示方法都是先通过一个tag表示自己的常量类型,再具体表示整个常量,比如CONSTANT_Utf8_info的结构如下
在这里插入图片描述
通过这种方法就能描述一个utf8型的常量。
表示完utf8类型之后,其它类型的常量(除了基本数据类型)的表示都是通过一个tag指定常量类型,再通过一个或者多个指向utf8的索引来拿到字符串,从而拼接出结构,比如CONSTANT_Class_info的结构如下:
在这里插入图片描述
比如6号常量是一个CONSTANT_Class_info型常量,它引用了25号常量,而25号常量是utf8类型,值为:”java/lang/Object“,因此就可以表示一个类。

4. access_flag

接下来两个字节是访问标志,访问标志用来表示一个类的访问信息,比如这个类是类、接口、枚举还是注解?这个类是public、private、final?由于这些信息只可能是是和否,所以可以通过标志位来表示,目前使用的标志位有8个,如下:
在这里插入图片描述
比如实例中声明了是public,而且是个普通的类继承自object类,因此有ACC_PUBLIC、ACC_SUPER两个标志。
在这里插入图片描述

5. this_class、super_class、interface_count、interface

在这里插入图片描述

  1. 接下来两个字节是类索引this_class,指向常量池的CONSTATN_class_info,从而可以知道这个类的全限定名。
  2. 再接着两个自己是父类索引super_class,也是指向常量池的CONSTATN_class_info。
  3. 再接着两个字节是接口数量interface_count,是一个常量,
  4. 其后的interface_count个指向常量池的CONSTATN_class_info的索引表示这个类实现的所有接口。

6. fields_count、fields_info

在这里插入图片描述
接下来两个字节是fields_count,表示这个类的字段个数,在接着是fields_count个的field_info结构体。field_info的结构如下:
在这里插入图片描述
首先两个字节是访问标志,表明字段是public、private、final?等,同样是采用标志位的表示方法,可选的标志如下:
在这里插入图片描述
实例中两个字段都是private的,因此标志位的两个字节都是0x0002。
再接着是两个字节的常量池索引,表示简单名称,比如字段name,指向常量池的0X0008,可以看到是字符串"name",再接着两个字节也是指向常量池的索引,表示字段的描述符,比如name字段的描述符是常量池0X0009的常量,是” Ljava/lang/String;“,表示这是一个字符串类型的字段。在接着是attributes_count,由于是0,所以后面就没有attribute属性了。

7. methods_count、methods_info

在这里插入图片描述
在这里插入图片描述

接下来两个字节是方法数methods_count,在接着是methods_count个methods_info结构体,methods_info结构体组成与fields_info相同:
在这里插入图片描述
区别主要在访问标志和描述符方面,访问标志去掉了volatile和transient,添加了synchronized、native、abstract、strictfp。描述符采用(参数标识符)返回值类型标识符的表示方法,比如void inc(),其方法描述符是”()V“。
方法里面重要的是属性表,方法内的代码都是存储在一个署名表中名为code的属性中。方法描述符接下来的两个字节是属性数量(attributes_count),再接着是attributes_count个attribute_info型结构体。对于code属性,其组成如下:
在这里插入图片描述
max_stack是操作数栈的最大深度,java虚拟机运行时操作数据都是基于栈(操作数栈)的,这个字段表示虚拟机操作数栈不会超过这个深度。
max_locals是局部变量表所需的存储空间,单位是槽slot,slot是虚拟机为局部变量分配内粗使用的最小单位,不超过32位的局部变量占用一个槽,64位的局部变量占用两个槽。这里max_locals值并不一定是局部变量占用槽的个数之和,因为有的槽是可以重用的。
code_length表示字节码指令个数,其后跟着code_length个字节码指令,每个字节码指令占用一个字节。
之后是异常表,表示方法中产生的异常,异常表不是必须的。
具体看一下实例,实例中第一个方法,也就是构造方法,有一个属性code,长度是0x00000027,说明后面39个字节都是这个code属性的内容。max_stack是2,max_local是1(也就是this),code_length是11,也就是所后面11个自己都是这个方法的字节码指令。再往后的exception_table_length是0,说明方法没有异常抛出。所以接着两个字节是attributes_count,说明有一个attribute,这个attribute是lineNumberTable属性,其组成如下:
在这里插入图片描述

8. attributes_count、attributes

在这里插入图片描述
最后就是class文件的属性了,接着的两个字节是属性个数,说明有一个属性,属性名是0x0012,就是常量池中18号常量”SourceFile“,长度位2,内容是常量池中0x0013,也就是”Test.java“,说明这个class文件的源码是Test.java
至此,一个完整的class文件结构就分析结束了。
书中这一章后面的小节主要讲常见的一些字节码指令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值