7. JVM详解之:java class文件的密码本
简介
一切的一切都是从javac开始的。从那一刻开始,java文件就从我们肉眼可分辨的文本文件,变成了冷冰冰的二进制文件。
变成了二进制文件是不是意味着我们无法再深入的去了解java class文件了呢?答案是否定的。
机器可以读,人为什么不能读?只要我们掌握java class文件的密码表,我们可以把二进制转成十六进制,将十六进制和我们的密码表进行对比,就可以轻松的解密 了。
下面,让我们开始这个激动人心的过程吧。
一个简单的class
为了深入理解java class的含义,我们首先需要定义一个class类:
public class JavaClassUsage {
private int age=18;
public void inc(int number){
this.age=this.age+ number;
}
}
很简单的类,我想不会有比它更简单的类了。
在上面的类中,我们定义了一个age字段和一个inc的方法。
接下来我们使用javac来进行编译。
IDEA有没有?直接打开编译后的class文件,你会看到什么?
没错,是反编译过来的java代码。但是这次我们需要深入了解的是class文件,于是我们可以选择 view->Show Bytecode:
当然,还是少不了最质朴的javap命令:
javap -verbose JavaClassUsage
对比会发现,其实javap展示的更清晰一些,我们暂时选用javap的结果。
编译的class文件有点长,我一度有点不想都列出来,但是又一想只有对才能讲述得更清楚,还是贴在下面:
public class com.flydean.JavaClassUsage
minor version: 0
major version: 58
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // com/flydean/JavaClassUsage.age:I
#8 = Class #10 // com/flydean/JavaClassUsage
#9 = NameAndType #11:#12 // age:I
#10 = Utf8 com/flydean/JavaClassUsage
#11 = Utf8 age
#12 = Utf8 I
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/flydean/JavaClassUsage;
#18 = Utf8 inc
#19 = Utf8 (I)V
#20 = Utf8 number
#21 = Utf8 SourceFile
#22 = Utf8 JavaClassUsage.java
{
public com.flydean.JavaClassUsage();
descriptor: ()V
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: bipush 18
7: putfield #7 // Field age:I
10: return
LineNumberTable:
line 7: 0
line 9: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/flydean/JavaClassUsage;
public void inc(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: aload_0
2: getfield #7 // Field age:I
5: iload_1
6: iadd
7: putfield #7 // Field age:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/flydean/JavaClassUsage;
0 11 1 number I
}
SourceFile: "JavaClassUsage.java"
ClassFile的二进制文件
慢着,上面javap的结果好像并不是二进制文件!
对的,javap是对二进制文件进行了解析,方便程序员阅读。如果你真的想直面最最底层的机器代码,就直接用支持16进制的文本编译器把编译好的class文件打开吧。
你准备好了吗?
来吧,展示吧!
上图左边是16进制的class文件代码,右边是对16进制文件的适当解析。大家可以隐约的看到一点点熟悉的内容。
是的,没错,你会读机器语言了!
class文件的密码本
如果你要了解class文件的结构,你需要这个密码本。
如果你想解析class文件,你需要这个密码本。
学好这个密码本,走遍天下都......没啥用!
下面就是密码本,也就是classFile的结构。
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 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中u2,u4表示的是无符号的两个字节,无符号的4个字节。
java class文件就是按照上面的格式排列下来的,按照这个格式,我们可以自己实现一个反编译器(大家有兴趣的话,可以自行研究)。
我们对比着上面的二进制文件一个一个的来理解。
magic
首先,class文件的前4个字节叫 做magic word。
看一下十六进制的第一行的前4个字节:
CA FE BA BE 00 00 00 3A 00 17 0A 00 02 00 03 07