本文系《深入理解Java虚拟机》总结

ClassFile{
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interface_count;
u2 interfaces[interface_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attribute_count;
attribute_info attributes[attribute_count];
}
  • Magic 魔数 固定值 0xCAFEBABE
  • minor_version 副版本号
  • major_version 主版本号
  • constant_pool_count 常量池计数器
  • constant_pool[] 常量池
  • access_flags 访问标志
  • this_class 类索引
  • super_class 父类索引
  • interface_count 接口计数器
  • interfaces[] 接口表
  • fields_count 字段计数器
  • fields[] 字段表
  • methods_count 方法计数器
  • methods[] 方发表
  • attribute_count 属性计数器
  • attributes[] 属性表

通过自己写的一个Java类编译来分析下

Java代码:

public class SimpleClazz {

    private int a = 3;

    public void add() {
int i = 0;
i++;
} }

Javap -verbose

public class com.fcs.test.SimpleClazz
SourceFile: "SimpleClazz.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#19 // com/fcs/test/SimpleClazz.a:I
#3 = Class #20 // com/fcs/test/SimpleClazz
#4 = Class #21 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/fcs/test/SimpleClazz;
#14 = Utf8 add
#15 = Utf8 i
#16 = Utf8 SourceFile
#17 = Utf8 SimpleClazz.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = NameAndType #5:#6 // a:I
#20 = Utf8 com/fcs/test/SimpleClazz
#21 = Utf8 java/lang/Object
{
public com.fcs.test.SimpleClazz();
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_3
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 6: 0
line 8: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/fcs/test/SimpleClazz; public void add();
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iinc 1, 1
5: return
LineNumberTable:
line 11: 0
line 12: 2
line 13: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/fcs/test/SimpleClazz;
2 4 1 i I
}

class真身:

CAFEBABE0000003300160A00040012090003001307001407001501000161010001490100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C650100047468697301001A4C636F6D2F6663732F746573742F53696D706C65436C617A7A3B0100036164640100016901000A536F7572636546696C6501001053696D706C65436C617A7A2E6A6176610C000700080C00050006010018636F6D2F6663732F746573742F53696D706C65436C617A7A0100106A6176612F6C616E672F4F626A65637400210003000400000001000200050006000000020001000700080001000900000038000200010000000A2AB700012A06B50002B100000002000A0000000A00020000000600040008000B0000000C00010000000A000C000D00000001000E000800010009000000420001000200000006033C840101B100000002000A0000000E00030000000B0002000C0005000D000B00000016000200000006000C000D000000020004000F0006000100010010000000020011

魔数、版本号

魔数就是4个字节的0xCAFEBABE,cafebabe

版本号分为副版本号和主版本号

如上图,副版本号为0x0000,主版本号为0x0033,即十进制的51,代表JDK1.7

常量池

首先是常量池的count 为u2类型 常量池的大小是1~count-1
然后是常量池具体内容 这是一个集合结构 不管什么类型的常量
tag都是u1类型 根据tag就可以找到具体常量类型 然后得到具体结构

00 16 0A 00 04 00 12 09 00 03 00

可得有21个常量 然后就是常量池内容
0A代表这是一个CONSTANT_Methodref_info类型的常量,知道其类型就知道了它的结构,然后它在字节码中就有了固定的长度。

tag u1
index u2 指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
index u2 指向字段描述符CONSTANT_NameAndType的索引项

根据上面编码可得 index=4 index=18

接着是第二个常量 tag=9 代表CONSTANT_Fieldref_info类型的常量 找到其结构可锁定其在字节码中的长度 就这样依次可得到所有常量

访问标志

00 21 00 03 00 04 ….

紧接着就是access_flags
access_flags中一共有16个标志位可以使用,当前只定义了其中8个,没有使用到的标志
位要求一律为0。以代码清单6-1中的代码为例,TestClass是一个普通Java类,不是接口、枚举或者注解,被public关键字修饰但没有被声明为final和abstract,并且它使用了JDK 1.2之后的编译器进行编译,因此它的ACC_PUBLIC、ACC_SUPER标志应当为真,而ACC_FINAL、ACC_INTERFACE、ACC_ABSTRACT、ACC_SYNTHETIC、ACC_ANNOTATION、ACC_ENUM这6个标志应当为假,因此它的access_flags的值应为:0x0001|0x0020=0x0021。

可以看出,access_flags标志(偏移地址:0x000000EF)的确为0x0021。

类索引、父类索引和接口索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集
合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。

接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。对于接口索引集合,入口的第一项——u2类型的数据为接口计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。

00 03 00 04 00 00 .. ..

可以看出该类索引指向第3个常量 内容为com/fcs/test/SimpleClazz
父类索引指向第4个常量 为java/lang/Object
接口索引集合中计数器值为0 所以没有实现任何接口

字段表集合

00 01 00 02 00 05 00 06 00 00 00 02

字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以
及实例级变量,但不包括在方法内部声明的局部变量。

字段表结构
u2 access_flag
u2 name_index
u2 descriptor_index
u2 attributes_count
attribute_info attributes

字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常类似的,都是一个u2的数据类型。

跟随access_flags标志的是两项索引值:name_index和descriptor_index。它们都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。

描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示。

对于数组类型,每一维度将使用一个前置的“[”字符来描述,如一个定义为“java.lang.String[][]”类型的二维数组,将被记录为:“[[Ljava/lang/String;”,一个整型数组“int[]”将被记录为“[I”。

如上
第一个u2类型的数据为容量计数器fields_count 0x0001 表示这个类只有一个字段表数据
access_flag 为 0x0002 代表private修饰符的ACC_PRIVATE标志位为真
name_index 索引指向第5个常量 是一个字符串a
descriptor_index 索引指向第6个常量 是描述符I 代表int类型
attributes_count 为0x0000 所以不再描述

方法表

方法表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项,见表6-11。这些数据项目的含义也非常类似,仅在访问标志和属性表集合的可选项中有所区别。

00 02 00 01 00 07 00 08 00 01 00 09

0x0002表示有两个方法,包括编译器添加的实例构造器<init>和源码中
的方法add()
access_flag 为0x0001 同样表示private
name_index 索引指向常量池中第7个常量
descriptor_index 索引指向第8个常量 ()V 表示返回值是void类型
attributes_count的值为0x0001就表示此方法的属性表集合有一项属性,属性名称索引为0x0009,对应常量为“Code”,说明此属性是方法的字节码描述。

属性表

在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。

对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占
用的位数即可。

u2 attribute_name_index
u4 attribute_length
u1 info

  • Code属性
    Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性
    内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬
    如接口或者抽象类中的方法就不存在Code属性。

Code属性表结构
u2 attribute_name_index 固定值Code
u4 attribute_length
u2 max_stack 操作数栈深度的最大值
u2 max_locals 局部变量所需的存储空间
u4 code_length
u1 code
u2 exception_table_length
exception_info exception_table 异常表
u2 attributes_count
attribute_info attributes

Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code,方法体里面的Java代码)和元数据(Metadata,包括类、字段、方法定义及其他信息)两部分,那么在整个Class文件中,Code属性用于描述代码,所有的其他数据项目都用于描述元数据。

00 02 00 01 00 00 00 0A 2A B7 00 01 2A 06 B5 00 02 B1

max_stack 0x0002 操作数栈最大深度为2
max_locals 0x0001 局部变量所需要的存储空间(单位Slot) 为1
code_length 0x0000000A 字节码长度为10
code 2A B7 00 01 2A 06 B5 00 02 B1

1)读入2A,查表得0x2A对应的指令为aload_0,这个指令的含义是将第0个Slot中为
reference类型的本地变量推送到操作数栈顶。

2)读入B7,查表得0xB7对应的指令为invokespecial,这条指令的作用是以栈顶的
reference类型的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private方法或者它的父类的方法。这个方法有一个u2类型的参数说明具体调用哪一个方法,它指向常量池中的一个CONSTANT_Methodref_info类型常量,即此方法的方法符号引用。

3)读入00 01,这是invokespecial的参数,查常量池得0x0001对应的常量为实例构造
器“<init>”方法的符号引用。

4)读入2A,同样对应的指令为aload_0,将第0个Slot中为reference类型的本地变量推送到操作数栈顶。

5)读入06,查表得0x06对应的指令为iconst_3,含义是将int型3推送至栈顶。

5)读入B5,putfield,含义是为指定类的实例域赋值,后面两个字节应该对应具体的实例域。

5)读入00 02,指向常量池中的 com/fcs/test/SimpleClazz.a:I,说明是给a赋值。

9)读入B1,查表得0xB1对应的指令为return,含义是返回此方法,并且返回值为void。
这条指令执行后,当前方法结束。


exception_info

00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 06 00 04 00 08 00 0B 00

然后是两个字节的code_lenght,为0x0000表示异常表长度为0,即没有异常块。
接着又是两个字节的 attributes_count ,说明存在2个属性表类型的结构。
0x000A attribute_name_index指向的是常量池中的第10个常量LineNumberTable

  • LineNumberTable属性
    LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。它并不是运行时必需的属性,但默认会生成到Class文件之中,可以在Javac中分别使用-g:none或-g:lines选项来取消或要求生成这项信息。

LineNumberTable属性结构
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info line_number_table

00 00 00 0A 00 02 00 00 00 06 00 04 00 08

0x0000000A代表的attribute_length为10,
0x0002代表 line_number_table_length为2

line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info表包括了start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源码行号。

字节码行号为0(0x0000) 对应Java源码行号为6(0x0006)
字节码行号为4(0x0004) 对应Java源码行号为8(0x0008)

接下来是第二个属性表结构

00 0B 00 00 00 0C

attribute_name_index为11,指向常量池中第11项LocalVariableTable

  • LocalVariableTable属性

LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,它也不是运行时必需的属性,但默认会生成到Class文件之中。

u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info local_variable_table

0x0000000C 表示attribute_length为12

00 01 00 00 00 0A 00 0C 00 0D 00 00

0x0001表示local_variable_table_length为1
0x0000表示start_pc为0
0x000A表示length为10
0x000C表示name_index为12,为this
0x000D表示descriptor_index为13,为Lcom/fcs/test/SimpleClazz;
0x0000表示index为0

local_variable_info项目结构
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index

start_pc和length属性分别代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围。

name_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符。

index是这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类型是64位类型时(double和long),它占用的Slot为index和index+1两个。

在JDK 1.5引入泛型之后,LocalVariableTable属性增加了一个“姐妹属性”:
LocalVariableTypeTable,这个新增的属性结构与LocalVariableTable非常相似,仅仅是把记录的字段描述符的descriptor_index替换成了字段的特征签名(Signature),对于非泛型类型来说,描述符和特征签名能描述的信息是基本一致的,但是泛型引入之后,由于描述符中泛型的参数化类型被擦除掉[3],描述符就不能准确地描述泛型类型了,因此出现了LocalVariableTypeTable。

接着分析方法表中的第二个方法

00 01 00 0E 00 08 00 01 00 09

access_flag 为0x0001 同样表示private
name_index 索引指向常量池中第14个常量,是utf类型常量“add”,表示add方法名
descriptor_index 索引指向第8个常量 ()V 表示返回值是void类型
attributes_count的值为0x0001就表示此方法的属性表集合有一项属性,属性名称索引为0x0009,对应常量为“Code”,说明此属性是方法的字节码描述。

此处分析与上面的构造方法大同小异。

总结:

  1. Class文件的格式代表的是一种规范,不管你的Java类是什么样的,按照这种规范去编译,得到的class文件总能被虚拟机所接受。

  2. 类的大小和内容都是不固定的,但是我们可以通过称为表的结构来表示这种复杂的内容,表中还可以套表,类似对象中包含对象一样。

  3. Class文件看似杂乱无章,其实有着Java虚拟机规范的约束,很容易翻译成虚拟机理解的内容。Java类只是为了方便我们理解和使用,这也就是所谓的高级之处。

  4. 从最外层看,Class文件有着固定的结构,从魔数,常量池到属性表,中间穿插了几种表,表中再来个表,这样显得非常灵活,不管有多少个表,顺着找下去,总有尽头,总有固定的字节数,我觉得解析的过程类似代码的执行顺序,遇到其他的类或者方法,就直接跳到那个去,执行完再回来。通过count和info这两个结构,化无限为有限。

Class文件格式的更多相关文章

  1. RIFF和WAVE音频文件格式

    RIFF file format RIFF全称为资源互换文件格式(Resources Interchange File Format),是Windows下大部分多媒体文件遵循的一种文件结构.RIFF文 ...

  2. JavaSe:Properties文件格式

    Properties文件格式说明 Properties继承自Hashtable,是由一组key-value的集合. 在Java中,常用properties文件作为配置文件.它的格式是什么样的呢? 下图 ...

  3. Dotnet文件格式解析

    0x0.序 解析过程并没有介绍对pe结构的相关解析过程,网上此类相关资料很多可自行查阅,本文只介绍了网上资料较少的从pe结构的可选头中的数据目录表中获取dotnet目录的rva和size,到完全解析d ...

  4. Reverse Core 第二部分 - 13章 - PE文件格式

    @date: 2016/11/24 @author: dlive ​ PE (portable executable) ,它是微软在Unix平台的COFF(Common Object File For ...

  5. iOS 图片文件格式判断、圆角图片

    1.圆角图片 // 设置圆形图片(放到分类中使用) - (UIImage *)cutCircleImage { UIGraphicsBeginImageContextWithOptions(self. ...

  6. 基于 Hive 的文件格式:RCFile 简介及其应用

    转载自:https://my.oschina.net/leejun2005/blog/280896 Hadoop 作为MR 的开源实现,一直以动态运行解析文件格式并获得比MPP数据库快上几倍的装载速度 ...

  7. 图解JVM的Class文件格式(详细版)

          了解JAVA的Class文件结构有助于掌握JAVA语言的底层运行机制,我在学习的过程中会不断的与ELF文件格式作对比(当然他们的复杂程度.格式相去甚远,比如可执行ELF的符号表解析在静态链 ...

  8. dex文件格式一

    一.生成dex文件 我们可以通过java文件来生成一个简单的dex文件 编译过程: 首先编写java代码如下: (1) 编译成 java class 文件 执行命令 : javac Hello.jav ...

  9. dex文件格式二

    一. dex文件头 (1) magic value 在DexFile.c   dexFileParse函数中 会先检查magic opt 啥是magic opt呢? 我们刚刚从cache目录拷贝出来的 ...

  10. stl文件格式

    http://wenku.baidu.com/view/a3ab7a26ee06eff9aef8077b.html [每个三角形面片的定义包括三角形各个定点的三维坐标及三角形面片的法矢量[三角形的法线 ...

随机推荐

  1. Create MSSQL Procedure

    代码: CREATE PROCEDURE [dbo].[sp_UpdateCouponCount] AS GO

  2. c# 事实证明,abstract类除了不能用new实例化和类没什么区别

    abstract类是抽象类,不能够实例化,大家都知道,abstract类往往和接口interface一块儿使用,针对接口中一些公共的方法进行实现,然后实体类去继承抽象类和接口.虽然abstract类不 ...

  3. 原生js实现单屏滚动

    类似于fullpage的单屏滚动,使用原生JS实现,不依赖任何js库: css: html,body {height:100%;} body {margin:0px;} div {height:100 ...

  4. 超详细的Java面试题总结(一)之Java基础知识篇

    面向对象和面向过程的区别 面向过程:   优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机.嵌入式开发.Linux/Unix等一般采用面向过程开发,性能是最重要的因 ...

  5. ribbon设置url级别的超时时间

    序 ribbon的超时设置,只能按转发的serviceId来分的,无法像nginx那样直接在每个转发的链接里头设置超时时间.这里hack一下,实现url基本的ribbon超时时间设置.具体的思路就是重 ...

  6. Python 16进制与字符串的转换

    电脑上装了Python2.7和3.3两个版本,平时运行程序包括在Eclipse里面调试都会使用2.7,但是由于某些原因在cmd命令行中输入python得到的解释器则是3.3, 一直没对此做处理,因为这 ...

  7. 玩转excel===Excel处理txt文件中的数据,Excel中的分列处理

    我的txt文件数据是这样的,目标是用第一列的数据生成图表: 现在我需要拿到pss列,用Excel的操作如下,先用Excel打开txt文档 所有数据都在A列,单独拿出来第一列数字.这时候要选择分列: o ...

  8. GLIBCXX_3.4.9' not found - 解决办法

    GLIBCXX_3.4.9' not found - 解决办法 http://blog.csdn.net/u012425536/article/details/26559653 https://koj ...

  9. 在LINUX平台上手动创建多个实例(oracle11g)

    在LINUX平台上手动创建多个实例(oracle11g) http://blog.csdn.net/sunchenglu7/article/details/39676659 ORACLE linux ...

  10. caffe Python API 之 数据输入层(Data,ImageData,HDF5Data)

    import sys sys.path.append('/projects/caffe-ssd/python') import caffe4 net = caffe.NetSpec() 一.Image ...