一、如何查看一个类文件的16进制结构
- 写一个简单的java程序,javac编译产生.class文件
- 用notepad++打开,一开始可能是乱码引入插件HexEditor
HexEditor.dllx64版本下载
将HexEditor.dll
文件放在Notepad++的plugins文件夹下面,重新打开文件
旁边会多出一个H字样点击之后就可以查看.class文件的16进制的格式了
程序代码:
public class Test{
private int m;
public int inc(){
return m+1;
}
}
二、文件结构
Class文件格式
其中u1,u2,u3表示1个字节,2个字节,3个字节
而类型带有“-info”的是有层次关系的复合结构数据。有点类似于结构体。
1.魔数(Magic Number)和版本(Minor Version、Major Version)
魔数:用于确定这个文件是否是一个被虚拟机接受的class文件。
很多文件存储标准都有魔数,使用魔数辨别文件类别比后缀名更加安全,后缀名可以轻易的改变,而魔数不能例如下面的JPG文件:
然后将其后缀名改为GIF:
魔数不会改变。这个值可以由文件制定者指定,没被广泛使用且不会引起混淆即可。
java的魔数cafebabe,咖啡宝贝hhh
版本号:将0x0034转为10进制就是52,下表中只记录了JDK1.7,而我是JDK1.8
JDK是向下兼容,即1.7的可以用1.6编译产生的Class文件,而不能用1.8编译产生的。
2.常量池(constant_pool)
常量池长度不固定,所以入口放置一项u2类型来指明有多少项常量
0x0013->19。表明有18项常量,为什么不是19项呢?
为了满足后面某些指向常量池的索引值的数据在特定的情况下表达“不应用任何一个常量池项目”
然后第一项常量0a->10记为#1
,这里又要查表了:
10是一个符号引用,然后在查表:
后面两个都是索引向,指向#4
和#15
,而4和15 我们还没有读到,就继续往下读
第二个0x09->9,项目类型表可知为字段引用符号。然后一直这样读下去,当有01类型时,就将数字对应的ASCII码记录下来,这就是我们能够读懂的地方了。
是不是很麻烦?而且很机械?既然知道怎么读了,当然是有程序可以帮你翻译的,但是还是要先搞清楚原理嘛
在cmd命令行输入
javap -verbose Test//Test为开始编译的文件名
- 1
- 2
获得以下结果:
常量池部分:
3.访问标志(access_flags)
用于识别一些类的或者接口层次的访问信息,包括是类还是接口,是不是public等等
如果是则将标志位做 | 运算,如果不是则不管,该类就应该是0x0001|0x0020 = 0x0021:
4.类索引(this_class),父类索引(super_class)与接口(interfaces)集合
这三项数据确定了这个类的继承关系。
除了java.lang.Object外所有java类的父类都不为0,因为object是所有类的粑粑。
接口索引集合大小为0,没有接口。
5.字段表集合(filed_info)
用于描述接口或者类中声明的变量
紧跟着接口集合后面的位容量计数器
本测试文件中的值位0x0001,即只有一个字段表数据
标志位0x0002–>ACC_PRIVATE为真,简单名称0x0005指向#5
,字段和方法描述符0x0006指向#6
,
标识符 | 含义 | 标识符 | 含义 |
---|---|---|---|
B | 基本类型byte | J | 基本类型long |
C | 基本类型char | S | 基本类型short |
D | 基本类型double | Z | 基本类型boolean |
F | 基本类型float | V | 特殊类型void |
I | 基本类型int | L | 对象类型。如Object |
在看我们写的程序是:
是不是就能理解了呢
6.方法表集合
其方法和字段表一样。
唯一不同的就是访问标志和属性表集合的可选项。图中我们可以解读出方法的信息,但是却没有执行的代码。这是因为经过编译器编译成字节码指令后存储在方法的属性集合中一个名为“Code”属性里面,也就是0x0009这个地方
7.属性表(attribute_info)集合
现在预定义的属性已经增加到了21个
属性表的结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
- Code属性
前面说过编译器编译成字节码指令后存储在方法的属性集合中一个名为“Code”属性里面。但接口或者抽象类就不存在Code属性
attribute_name_index是一个指向指向utf8型常量的索引,常量值固定为“Code”。
由于属性名和属性长度一共为6字节,所以属性值的长度位整个属性表的长度减去6。
max_stack代表操作数栈深度的最大值。虚拟机运行时根据这个值来分配栈帧。
max_locals代表局部变量表所需的存储空间。单位是Slot
Slot是虚拟机为局部变量分配内存所要使用的最小单位。对于byte、char、float、int、short、boolean 和returnAddress等长度不超过32的占用1个Slot,double和long占用2个,且局部变量表中的Slot可以重用
code_length代表字节码的长度。虽然他是一个u4类型,但实际长度只用了u2。如果超过了javac会拒绝编译。但有些时候复杂的JSP文件用某些JSP编译器会将JSP页面的内容和输出信息归到一个方法之中,从而导致编译失败
看看代码
public class Test{
private int m;
public int inc(){
return m+1;
}
}
初始化方法init()
和inc()
都没有参数但是Args_size()
都为1。这是因为我们常用的this
就是这个参数。实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留出第一个Slot位来存放对象实例的引用