类文件结构
1.1 魔数与class文件的版本
class文件的前4个字节表示文件类型(16进制无符号数0XCAFEBABE, 1个16进制需要二进制4位表示, 8个16进制数, 需要32位2进制数正好4个字节).
紧随魔数之后的4个字节表示class文件的版本号.
使用vim查看class文件, 使用 :%!xxd 命令查看16进制, 结果如下图:

1.2 常量池
class文件的格式包含: 无符号数和表.
无符号数容易理解, 可以表示数字、索引引用、数量值或者按照UTF8编码构成字符串.
表由多个无符号数或者其他表作为数据项组成的复合数据结构, 习惯性以 _info 结尾.
表用于描述有层次关系的复合结构的数据, 整个class文件本质上就是一张表.
常量池是class文件的第一个表, 文件版本号的后面就是常量池, 前两个字节表示常量池的容量, 如上图的:0X0050, 表示十进制80, 其实只有79个常量, 因为第0号位需要空出来, 当后面数据要表示“不引用任何一个常量池项目”时, 可以把索引值置为0来实现.
将class文件进行javap反编译后, 可以看到常量池中确实只有79个.

如果要了解class文件的javap命令的翻译逻辑, 就需要先了解如下这个类型列表:

逻辑如下:
0x0050这是常量池的项目数量,0x0a是十进制10, 通过上面的表可知tag=10的是CONSTANT_Methodref_info, 然后这个表的下一部分是u2, u2表示两个字节, 用来表示这个方法所属的类, 对应的十六进制数是0x0011, 即17, 然后CONSTANT_Methodref_info这个表的最后一部分是一个u2, 使用同样的方式在class的图中找, 可以得到0x002d, 即45, 至此, javap中#1的Methodref的#17.#45就是这么来的, 以此类推即可得到javap的整个反编译内容.
题外话:
CONSTANT_Utf8_info最大长度是2个字节表示, 2^16=65536, 这解释了为什么变量名称和方法名称等不能超过64KB.
1.3 访问标志
常量池之后的两个字节就是访问标志access_flags, 标识类或者接口层次的访问信息, 包括:这个class是类还是接口; 是否是pulbic; 是否是abstract; 是否final;
访问标志的取值从下表中的值求和得到:


1.4 类索引、父类索引与接口索引集合
class文件中一定会包含: 这个class的类的全名、父类的全名、实现的接口, class中依次排列这三种信息.
其中实现的接口是集合的形式, 集合种元素的顺序就是源码中 implements 后面接口的顺序.
类索引、父类索引都是u2类型, 指向常量池中的对应项.
接口索引集合首先是2个字节表示的集合元素数量, 如果为元素数量为0, 则没有集合内容部分;如果元素数量不为0, 其中的元素长度都是两个字节的常量池索引值.

下图是截取的另一个类:

1.5 字段表集合
类的字段指的是类变量和实例变量, 不包含局部变量.
在上述的接口索引集合之后, 使用u2表示字段数量, 然后开始逐一列举字段.
字段表的每一个字段结构如下:

上图中的attribute_info就是属性表, 将在下文介绍.


综上可见, 0x00020002000e000f的含义就是 private String a; 这样一句代码.
关于简单名称、描述符、 全限定名:
全限定名与类的全名很像, 就是把" . " 换成 " / " , 通常一个全限定名结束后会以 " ; " 结尾, 以防止多个全限定名之间产生混淆.
简单名称: 方法ins(), 简单名称就是ins, 不要返回值和参数, 也不要括号. 字段a的简单名称就是a.
描述符: java.lang.String的toString方法, 描述符:" ()Ljava/lang/String; ", 除了方法名看不出来, 其他都会显示在其中, 方法参数列表在前, 返回值在后, 返回值先用标识符L表示是什么类型, 在跟上类的全限定名.
方法 int a(int x,Integer y, String[] z), 描述符是:" (ILjava/lang/Integer;[Ljava/lang/String;)I ", 可见, 数组类型会在数组元素描述之前加一个" [ ". 而对于字段的描述符就是描述字段的类型, 例如private String a; 描述符是:" Ljava/lang/String; "

字段表中不会列出超类或上层接口中继承的字段, 但内部类中可能会出现外部类的字段, 已实现对外部类的访问性.
1.6 方法表集合

方法表与字段表使用的策略十分相似, 只是access_flags的可选项有所不同.
方法表的前面一定会有一个u2, 表示表的元素数量, 之后依次列举元素.
至于方法中的代码, 则被存储于方法表的属性表中, 属性表的“code”这一项中将包含该信息, 属性表就是上图中attribute_info, 字段表中的字段也可能具备该结构, 需要看这个字段是否是有附加信息(如static final修饰的字段).

1.7 属性表集合
属性表在class文件、字段表、方法表中都有.
属性表中的项没有顺序要求, 可以自定义添加新的项, 虚拟机会自动忽略不认识的项.

上图中的17个code部分, 每一个字节都是一个字节码指令. 例如第一个字节码0x2a, 就表示aload.

从javap的显示可以看到构造方法args_size=1, 但是源码中并没有发现有参数, 这个参数就是this. 如果是静态方法就不会有这个arg了.
从javap的方法表的LocalVariableTable可见局部变量表的slot: 0 就是this, start: 0, length: 17, 表示这个变量的生命周期从code:0 到 code:16.
泛型中参数化类型(用来记录对象原本类型的信息)存储于Signature,LocalVariableTypeTable是泛型的特征签名.
