JVM之Class文件分析详解
平台无关性
Java 是与平台无关的语言,这得益于 Java 源代码编译后生成的存储字节码的文件,即 Class 文件,以及 Java 虚拟机的实现。
不仅使用 Java 编译器可以把 Java 代码编译成存储字节码的 Class 文件,使用 JRuby 等其他语言的编译器也可以把程序代码编译成 Class 文件,虚拟机并不关心 Class 的来源是什么语言,只要它符合一定的结构,就可以在 Java 中运行。
image.pngJava 语言中的各种变量、关键字和运算符的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定会比 Java 语言本身更强大,这便为其他语言实现一些有别于 Java 的语言特性提供了基础,而且这也正是在类加载时要进行安全验证的原因。
类文件结构
类文件是java程序编译后产生的文件,要了解java虚拟机,了解它的类文件结构是必不可少的。
Class 文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件中,中间没有添加任何分隔符,这使得整个 Class 文件中存储的内容几乎全部都是程序运行的必要数据。
根据 Java 虚拟机规范的规定,Class 文件格式采用一种类似于 C 语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。
无符号数属于基本数据类型,以 u1、u2、u4、u8 来分别代表 1、2、4、8 个字节的无符号数。
表是由多个无符号数或其他表作为数据项构成的符合数据类型,所有的表都习惯性地以“_info”结尾。
整个 Class 文件本质上就是一张表,它由如下所示的数据项构成。
从表中可以看出,无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的该数据项的形式,称这一系列连续的摸一个类型的数据为某一类型的集合,比如,fields_count 个 field_info 表数据构成了字段表集合。这里需要说明的是:Class 文件中的数据项,都是严格按照上表中的顺序和数量被严格限定的,每个字节代表的含义,长度,先后顺序等都不允许改变。
接下来,用一个例子,逐个字节分析Class文件。了解Class文件内部结构。
原始类文件:
这个类直接拿了知乎的一个类,分析是很简单,只是因为重新画图比较麻烦,不想再做重复的苦力工作(地址(https://zhuanlan.zhihu.com/p/23068093)ps:作者仅仅提供了类和图,并无解析。)
package com.vonzhou.learn.jvm.klass;
public class Foo {
private int m;
public int inc() {
return m + 1;
}
}
Class结构体
一个Class文件可以用如下的结构体抽象:
在这里插入图片描述Step by Step
接下来就是对照着字节码和JVM规范阅读的过程,对自己多点耐心。
Class魔数和版本
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述常量池计数器和常量池
在这里插入图片描述注意:常量池内常量的真实数量是 常量池计数器-1
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 img 在这里插入图片描述 在这里插入图片描述 img 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 img img 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述那么接下来就是这三个了:
1556854982133访问标志
1556855229233 1556851149393这里
img为什么是0021呢?因为 0X0001|0X0020 = 0X0021。
类索引,父类索引
1556855257629接下来的2B是this_class指向我们的类名, super_class指示父类。
img img这里没有实现接口:
img字段计数器和字段表集合
fields_count以后进入字段表集合
1556851584435 1556851428143 1556851512040 img方法计数器和方法表集合
首先进入方法计数器。
1556852236787 img 1556852171723 1556851428143从上可以看出,关键的不同点在于attribute_info字段。这是属性表集合,下面附上属性表集合的一般结构。
1556852397696 1556852498092从上面两个图可以看出属性表前两个字段时固定的,关键在于第三个字段各有不同,比如方法表的属性表Code的完整格式为下图
1556853118948接下来就对Code属性表进行梳理:
img img属性计数器和属性表集合
1556856126125 属性表集合中的SourceFile: 1556854138367 1556854215807由属性计数器后的0010计算十进制数为
1556855978446 下面的DEC为16,所以找到第16个常量为 1556856030704没错了,是SourceFile
img附上javap -version 验证解析结果
使用javap解析出来的结果中没有包含LocalVariableTable字段,需要在使用javac编译java中时,加上-g的参数,生成的class文件中才带有LocalVariableTable的信息。LocalVariableTable属性:用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,非运行时必需属性,默认不会生成至Class文件中,可以使用Javac的-g:none或-g:vars关闭或要求生成该项属性信息。
1556860378700另外:LineNumberTale属性:用于描述Java源码的行号与字节码行号之间的对应关系,非运行时必需属性,会默认生成至Class文件中,可以使用Javac的-g:none或-g:lines关闭或要求生成该项属性信息。
1556860512094具体编译过程:
javac -g -d . Foo.java
javap -v com.vonzhou.learn.jvm.klass.Foo
ca fe ba be 20 20 20 34 20 16 0a 20 04 20 12 09 20 03 20 13 07 20 14 07 20 15 01 20 01 6d 01 20 01 49 01 20 06 3c 69 6e 69 74 3e 01 20 03 28 29 56 01 20 04 43 6f 64 65 01 20 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 20 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 20 04 74 68 69 73 01 20 21 4c 63 6f 6d 2f 76 6f 6e 7a 68 6f 75 2f 6c 65 61 72 6e 2f 6a 76 6d 2f 6b 6c 61 73 73 2f 46 6f 6f 3b 01 20 03 69 6e 63 01 20 03 28 29 49 01 20 0a 53 6f 75 72 63 65 46 69 6c 65 01 20 08 46 6f 6f 2e 6a 61 76 61 0c 20 07 20 08 0c 20 05 20 06 01 20 1f 63 6f 6d 2f 76 6f 6e 7a 68 6f 75 2f 6c 65 61 72 6e 2f 6a 76 6d 2f 6b 6c 61 73 73 2f 46 6f 6f 01 20 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 20 21 20 03 20 04 20 20 20 01 20 02 20 05 20 06 20 20 20 02 20 01 20 07 20 08 20 01 20 09 20 20 20 2f 20 01 20 01 20 20 20 05 2a b7 20 01 b1 20 20 20 02 20 0a 20 20 20 06 20 01 20 20 20 03 20 0b 20 20 20 0c 20 01 20 20 20 05 20 0c 20 0d 20 20 20 01 20 0e 20 0f 20 01 20 09 20 20 20 31 20 02 20 01 20 20 20 07 2a b4 20 02 04 60 ac 20 20 20 02 20 0a 20 20 20 06 20 01 20 20 20 07 20 0b 20 20 20 0c 20 01 20 20 20 07 20 0c 20 0d 20 20 20 01 20 10 20 20 20 02 20 11
E:\JVM>javap -v com.vonzhou.learn.jvm.klass.Foo
Classfile /E:/JVM/com/vonzhou/learn/jvm/klass/Foo.class
Last modified 2019-5-3; size 391 bytes
MD5 checksum 4d9e593620f49a9114d834ec5d923986
Compiled from "Foo.java"
public class com.vonzhou.learn.jvm.klass.Foo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#19 // com/vonzhou/learn/jvm/klass/Foo.m:I
#3 = Class #20 // com/vonzhou/learn/jvm/klass/Foo
#4 = Class #21 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/vonzhou/learn/jvm/klass/Foo;
#14 = Utf8 inc
#15 = Utf8 ()I
#16 = Utf8 SourceFile
#17 = Utf8 Foo.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = NameAndType #5:#6 // m:I
#20 = Utf8 com/vonzhou/learn/jvm/klass/Foo
#21 = Utf8 java/lang/Object
{
public com.vonzhou.learn.jvm.klass.Foo();
descriptor: ()V
flags: 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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/vonzhou/learn/jvm/klass/Foo;
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/vonzhou/learn/jvm/klass/Foo;
}
SourceFile: "Foo.java"
再次附上没有 -g 的结果:
ca fe ba be 20 20 20 34 20 13 0a 20 04 20 0f 09 20 03 20 10 07 20 11 07 20 12 01 20 01 6d 01 20 01 49 01 20 06 3c 69 6e 69 74 3e 01 20 03 28 29 56 01 20 04 43 6f 64 65 01 20 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 20 03 69 6e 63 01 20 03 28 29 49 01 20 0a 53 6f 75 72 63 65 46 69 6c 65 01 20 08 46 6f 6f 2e 6a 61 76 61 0c 20 07 20 08 0c 20 05 20 06 01 20 1f 63 6f 6d 2f 76 6f 6e 7a 68 6f 75 2f 6c 65 61 72 6e 2f 6a 76 6d 2f 6b 6c 61 73 73 2f 46 6f 6f 01 20 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 20 21 20 03 20 04 20 20 20 01 20 02 20 05 20 06 20 20 20 02 20 01 20 07 20 08 20 01 20 09 20 20 20 1d 20 01 20 01 20 20 20 05 2a b7 20 01 b1 20 20 20 01 20 0a 20 20 20 06 20 01 20 20 20 03 20 01 20 0b 20 0c 20 01 20 09 20 20 20 1f 20 02 20 01 20 20 20 07 2a b4 20 02 04 60 ac 20 20 20 01 20 0a 20 20 20 06 20 01 20 20 20 07 20 01 20 0d 20 20 20 02 20 0e
E:\JVM>javac -d . Foo.java
E:\JVM>javap -v com.vonzhou.learn.jvm.klass.Foo
Classfile /E:/JVM/com/vonzhou/learn/jvm/klass/Foo.class
Last modified 2019-5-3; size 291 bytes
MD5 checksum 45262c23d72e75c78347d2f05b918bee
Compiled from "Foo.java"
public class com.vonzhou.learn.jvm.klass.Foo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // com/vonzhou/learn/jvm/klass/Foo.m:I
#3 = Class #17 // com/vonzhou/learn/jvm/klass/Foo
#4 = Class #18 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 Foo.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // m:I
#17 = Utf8 com/vonzhou/learn/jvm/klass/Foo
#18 = Utf8 java/lang/Object
{
public com.vonzhou.learn.jvm.klass.Foo();
descriptor: ()V
flags: 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 3: 0
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 7: 0
}
SourceFile: "Foo.java"