本文使用Java版本 1.8.0_202
JVM版本 HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
参考书籍:《Java虚拟机规范.Java SE 8版》
首先我们定义一个源文件
package com.yuxuan66.jvm.classdef;
/**
* @author Sir丶雨轩
* @since 2022/6/6
*/
public class Hello {
public static String msg = "World";
public static void main(String[] args) {
System.out.println("Hello " + msg);
}
}
然后我们运行得到编译后的Class文件,通过javap把Class文件还原为Java编译器生成的字节码
javap -verbose ./Hello.class
-verbose 输出栈大小,方法参数的个数
我们会得到如下的字节码
Classfile /F:/Developer/Java/JVMStudy/out/production/JVMStudy/com/yuxuan66/jvm/classdef/Hello.class
Last modified 2022年6月6日; size 840 bytes
SHA-256 checksum ab48ccf6fcf0d882a5a0e6825526ba8c890efeea38631f71bcfe2d844216d63f
Compiled from "Hello.java"
public class com.yuxuan66.jvm.classdef.Hello
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #11 // com/yuxuan66/jvm/classdef/Hello
super_class: #12 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #12.#29 // java/lang/Object."<init>":()V
#2 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Class #32 // java/lang/StringBuilder
#4 = Methodref #3.#29 // java/lang/StringBuilder."<init>":()V
#5 = String #33 // Hello
#6 = Methodref #3.#34 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#7 = Fieldref #11.#35 // com/yuxuan66/jvm/classdef/Hello.msg:Ljava/lang/String;
#8 = Methodref #3.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = String #39 // World
#11 = Class #40 // com/yuxuan66/jvm/classdef/Hello
#12 = Class #41 // java/lang/Object
#13 = Utf8 msg
#14 = Utf8 Ljava/lang/String;
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 LocalVariableTable
#20 = Utf8 this
#21 = Utf8 Lcom/yuxuan66/jvm/classdef/Hello;
#22 = Utf8 main
#23 = Utf8 ([Ljava/lang/String;)V
#24 = Utf8 args
#25 = Utf8 [Ljava/lang/String;
#26 = Utf8 <clinit>
#27 = Utf8 SourceFile
#28 = Utf8 Hello.java
#29 = NameAndType #15:#16 // "<init>":()V
#30 = Class #42 // java/lang/System
#31 = NameAndType #43:#44 // out:Ljava/io/PrintStream;
#32 = Utf8 java/lang/StringBuilder
#33 = Utf8 Hello
#34 = NameAndType #45:#46 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#35 = NameAndType #13:#14 // msg:Ljava/lang/String;
#36 = NameAndType #47:#48 // toString:()Ljava/lang/String;
#37 = Class #49 // java/io/PrintStream
#38 = NameAndType #50:#51 // println:(Ljava/lang/String;)V
#39 = Utf8 World
#40 = Utf8 com/yuxuan66/jvm/classdef/Hello
#41 = Utf8 java/lang/Object
#42 = Utf8 java/lang/System
#43 = Utf8 out
#44 = Utf8 Ljava/io/PrintStream;
#45 = Utf8 append
#46 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#47 = Utf8 toString
#48 = Utf8 ()Ljava/lang/String;
#49 = Utf8 java/io/PrintStream
#50 = Utf8 println
#51 = Utf8 (Ljava/lang/String;)V
{
public static java.lang.String msg;
descriptor: Ljava/lang/String;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
public com.yuxuan66.jvm.classdef.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 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/yuxuan66/jvm/classdef/Hello;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String Hello
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: getstatic #7 // Field msg:Ljava/lang/String;
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
LineNumberTable:
line 12: 0
line 13: 27
LocalVariableTable:
Start Length Slot Name Signature
0 28 0 args [Ljava/lang/String;
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #10 // String World
2: putstatic #7 // Field msg:Ljava/lang/String;
5: return
LineNumberTable:
line 9: 0
}
SourceFile: "Hello.java"
我们这里使用Notepad++来阅读Class文件的十六进制代码,需要安装插件Hex-Editer
首先我们来看一下Class文件的结构
这个u?就代表字节数
首先第一个u4 = 魔数
魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定0xCAFEBABE,不会改变
接下来两个u2分别代表副版本号和主版本号
我们可以看到副版本号为 00 00 则值为0
主版本号为 00 34 这里是十六进制,我们转换为10进制 3 * 16 + 4 = 52
我们可以在下面这张表上面看到52=Java 8
现在我们进行下一个u2 constan_pool_count 这个代表常量池的数量
00 34 转换为10进制,我们知道一共有52个常量,计算方法可以参考上面版本号或者百度进制转换
在JVM规范中如果常量池的数量不为0,这里一般都是不为0的。下面的数据为常量池的具体数据,简称cp_info
我们可以在JVM规范中找到cp_info的定义
常量池tag的含义
我们得知接下来的u1为常量的标记
0a转换10进制 = 10,这里代表CONSTANT_Methodref 我们需要从JVM规范中找到对应的解释
我们得知接下来的u1为方法的类型,u2为类的索引,u2为名称和类型的索引
这些索引是指在常量池中的索引,由于我们还没有分析完,所以我们可以对照上面javap给出的结果来查看
u1 tag; = 0a = 10
u2 class_index; = 0c = 12
#12 = Class #41 // java/lang/Object
#41 = Utf8 java/lang/Object
我们可以看到这个类的全部类名
u2 name_and_type_index; = 1d = 29
#29 = NameAndType #15:#16 // "<init>":()V
#15 = Utf8 <init>
#16 = Utf8 ()V
我们可以看到这个方法叫
<init> <cinit> 是JVM为我们生成的特殊方法
我们接着往下看
09 根据上面的数据我们可以得知这是一个字段的定义,CONSTANT_Fieldref
然后我们去JVM规范中找到他的描述
我们看到这个是跟方法的定义很像的
u1 tag; = 09 = 9
u2 class_index; = 1e = 30
#30 = Class #42 // java/lang/System
#42 = Utf8 java/lang/System
u2 name_and_type_index; = 1f = 31
#31 = NameAndType #43:#44 // out:Ljava/io/PrintStream;
#43 = Utf8 out
#44 = Utf8 Ljava/io/PrintStream;
通过以上的数据我们可以看到是System里面的out字段 字段类型为 java/io/PrintStream
依次往后只需要遵循JVM规范即可阅读整个Class文件
评论区