Open bitfishxyz opened 5 years ago
点解图片来查看大图
上面就是class文件的二级制格式和javap输出结果的对应关系。
为了让读者可以感性的了解,我花了一下午,根据jvm规范的规则,把二级制格式和javap的结果做了对比。。。走过路过的朋友,赏个star做辛苦费吧。。。
这里我写了一个新的类,它编译后的class文件是这样的,你能够读出来这个类的特征吗?
$ javac Hello.java
$ javap -v Hello.class
Classfile /Users/apple/Downloads/x1hnd1rk/TemplateJava/src/Hello.class
Last modified Jul 5, 2019; size 412 bytes
MD5 checksum f67b1372d221fbabc8cab2a175f87060
Compiled from "Hello.java"
public class Hello
minor version: 0
major version: 54
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // Hello
super_class: #6 // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #6.#16 // java/lang/Object."<init>":()V
#2 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #19 // Hello world
#4 = Methodref #20.#21 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #22 // Hello
#6 = Class #23 // java/lang/Object
#7 = Utf8 age
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 sayHello
#14 = Utf8 SourceFile
#15 = Utf8 Hello.java
#16 = NameAndType #9:#10 // "<init>":()V
#17 = Class #24 // java/lang/System
#18 = NameAndType #25:#26 // out:Ljava/io/PrintStream;
#19 = Utf8 Hello world
#20 = Class #27 // java/io/PrintStream
#21 = NameAndType #28:#29 // println:(Ljava/lang/String;)V
#22 = Utf8 Hello
#23 = Utf8 java/lang/Object
#24 = Utf8 java/lang/System
#25 = Utf8 out
#26 = Utf8 Ljava/io/PrintStream;
#27 = Utf8 java/io/PrintStream
#28 = Utf8 println
#29 = Utf8 (Ljava/lang/String;)V
{
int age;
descriptor: I
flags: (0x0000)
public Hello();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public void sayHello();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 4: 0
line 5: 8
}
SourceFile: "Hello.java"
public class Hello
minor version: 0
major version: 54
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // Hello
super_class: #6 // java/lang/Object
上面这个片段中,minor version 和 major version就是我们的class文件的版本号,它的值是54。这个版本和我们的java语言的版本的对应关系如下:
Java版本 class文件版本
1.0.2 45.0 ≤ v ≤ 45.3
1.1 45.0 ≤ v ≤ 45.65535
1.2 45.0 ≤ v ≤ 46.0
1.3 45.0 ≤ v ≤ 47.0
1.4 45.0 ≤ v ≤ 48.0
5.0 45.0 ≤ v ≤ 49.0
6 45.0 ≤ v ≤ 50.0
7 45.0 ≤ v ≤ 51.0
8 45.0 ≤ v ≤ 52.0
9 45.0 ≤ v ≤ 53.0
10 45.0 ≤ v ≤ 54.0
11 45.0 ≤ v ≤ 55.0
当前的是java10的版本。
而上面的this_class字段就是我们当前类的类名,它的值是#5
,也就是字符串常量池中索引为5的位置,也就是Hello
,所以我们当前类的类名是Hello
。
同理,通过super_class这个字段知道我们当前类的父类是java.lang.Object
这个类。
而通过flags: (0x0021) ACC_PUBLIC, ACC_SUPER
这个字段可知我们的类是public的类。
interfaces: 0, fields: 1, methods: 2, attributes: 1
通过这里我们可以读出,这个类有一个字段,两个方法,没有实现其他接口。
int age;
descriptor: I
flags: (0x0000)
说明这个类有个int类型的字段age,使用的是默认的访问修饰符。
还有连个方法 public Hello()
public void sayHello()
,方法体的内容暂时不用管。
综合就是这样的:
public class Hello {
int age;
public Hello(){
// ...
}
public void sayHello(){
//...
}
}
这里也可以看出,我们不需要读懂class文件的二级制格式,只需要读懂javap
的结果就行了。
我们知道我们的Java代码最终会编译成class文件,然后我们把class文件交给jvm来执行。所以就有了下面的问题:
好,接下我们就来讨论这些内容。
编译
.java
文件我们知道,我们编写的java源代码文件都是以
.java
结尾的。.java
文件对于人类来说是便于阅读的,.class
文件对于机器来说是便于阅读的。编译的本质就是把对机器不友好的文件格式转化为便于JVM理解的文件。首先呢,我们来写一个最简单的Java类:
这个类看起没有任何多余的信息。但是我们知道,Java语言有下面的规则:
java.lang.Object
这个类所以实际的类应该是这样的:
这一点大家知道就行了,我们在分析编译后的class文件时,能够证明这一点。
然后我们用
javac
编译一下上面的类:不出意外的话,我们就可以拿到我们的
Hello.class
文件了。查看class文件的二进制格式
class文件是一个二级制文件,我们需要使用能够解析二进制文件的工具来查看。这里我使用了
xxd
,你可以使用其他工具。xxd的用法
上面就是编译后的class文件的内容。注意,它是用16进制来展示的。
哎呀,天书,好像有点看不懂。
没关系,首先我们来看看前面的
cafe babe
。我们知道,我们的磁盘上的文件有png类型的、pdf类型的等,不同的文件格式有不同的处理规则。那么我们的应用程序如何区分不同的文件类型呢?
一种办法就是使用后缀名
我们约定所有以
.class
文件结尾的文件就是我们jvm能够处理的文件。 但是这种策略的弊端就是:文件名可以随意的修改,约束性不强。另一种就是用的 maginc number
关于maginc number你可以看看这个知乎的问题
但是后面的字节是什么意思呢?看起来好像不太清楚。我们需要使用其他工具来帮我我们。
使用javap来查看
这里我们
javep
看一下,这是一款jdk自带的开发工具。上面的信息就是我们的二进制文件,按照jvm语言规范解析出来的。
这时候你可能会有疑问,为什么我们的字节码可以转换为上面的格式,或者说转化的规则是什么?
要解决这个问题你就需要详细阅读JVM规范了。具体的规则对我们学习JVM字节码来说并不是很重要,它只对jvm的实现者和一些操作字节的框架的作者来说有用,所以我这里就忽略。如果你很敢兴趣,那么你可以自行阅读JVM规范,后续的内容我都是基于javap的结果来介绍的。
这里我提供了一些图片,介绍了字节码和javap生成的分析文件的关系。大家可以感性的了解一下。