文章目录
当你迷茫的时候,请点击JVM 目录大纲 快速查看前面的技术文章,相信你总能找到前行的方向
前言
上一篇 JVM 深入研究 -- 详解class 文件 结合官网文档分析 class 文件示例,作为 JVM 深入学习的开篇,今天来通过工具来简化分析 class 的步骤
A.java
先上原代码,回顾一下故事的主角:
java
package basic.object;
public class A {
// 一段rap
private String song = "你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!";
public void rap() {
System.out.println(this.song);
}
public static void main(String[] args) throws Exception {
A obj = new A();
obj.rap();
obj.getClass();
while (true){
}
}
}
A.class
执行javac A.java
原码编译后形成 A.class 文件,是一堆十六进制的字节码,通过上节分析,展示一下它的区块全貌:

分析一下前16个字节含义:CAFEBABE 0000 0034 0034 0A 000A 0020 08
-
前 4 字节:
CA FE BA BE
(魔数)这是 Java class 文件的标志性开头,称为 "魔数"(Magic Number)
作用:用于 JVM 快速识别是否为有效的 class 文件
十六进制 "CAFEBABE" 对应英文 "咖啡宝贝",是 Java 语言的标志性设计
-
接下来 4 字节:
0000 0034
(版本号)前 2 字节(
0000
):minor_version(次版本号)= 0后 2 字节(
0034
):major_version(主版本号)= 52(十六进制 34 转换为十进制是 52)版本对应关系:Java 8 的主版本号是 52,因此这个 class 文件是由 Java 8 编译产生的
-
接下来 2 字节:
0034
(常量池长度)表示常量池中有 52 项(34 十六进制 = 52 十进制)
注意:常量池索引从 1 开始,因此实际有效索引范围是 1~52,共 52 项
-
剩余 6 字节:
0A 00 0A 00 20 08
(常量池前 3 项)class 文件在版本号和常量池长度之后,就是常量池的具体内容,每一项都是一个常量:
第 1 字节(
0A
):表示常量池第 1 项的类型是CONSTANT_Methodref_info
(方法引用)接下来 2 字节(
000A
):指向声明方法的类的索引,对应常量池第 10 项接下来 2 字节(
0020
):指向方法名称和描述符的索引,对应常量池第 32 项第 6 字节(
08
):表示常量池第 2 项的类型是CONSTANT_String_info
(字符串常量)...
javap 反编译 class
javap 命令查看字节码 javap -v A.class
java
Classfile /path/to/java-oops/target/classes/basic/object/A.class
Last modified 2025-9-26; size 969 bytes
MD5 checksum 7b641ea8aee513d0afbf8492ea0fd19e
Compiled from "A.java"
public class basic.object.A
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#32 // java/lang/Object."<init>":()V
#2 = String #33 // 你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!
#3 = Fieldref #6.#34 // basic/object/A.song:Ljava/lang/String;
#4 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #39 // basic/object/A
#7 = Methodref #6.#32 // basic/object/A."<init>":()V
#8 = Methodref #6.#40 // basic/object/A.rap:()V
#9 = Methodref #10.#41 // java/lang/Object.getClass:()Ljava/lang/Class;
#10 = Class #42 // java/lang/Object
#11 = Utf8 song
#12 = Utf8 Ljava/lang/String;
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 Lbasic/object/A;
#20 = Utf8 rap
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 obj
#26 = Utf8 StackMapTable
#27 = Class #39 // basic/object/A
#28 = Utf8 Exceptions
#29 = Class #43 // java/lang/Exception
#30 = Utf8 SourceFile
#31 = Utf8 A.java
#32 = NameAndType #13:#14 // "<init>":()V
#33 = Utf8 你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!
#34 = NameAndType #11:#12 // song:Ljava/lang/String;
#35 = Class #44 // java/lang/System
#36 = NameAndType #45:#46 // out:Ljava/io/PrintStream;
#37 = Class #47 // java/io/PrintStream
#38 = NameAndType #48:#49 // println:(Ljava/lang/String;)V
#39 = Utf8 basic/object/A
#40 = NameAndType #20:#14 // rap:()V
#41 = NameAndType #50:#51 // getClass:()Ljava/lang/Class;
#42 = Utf8 java/lang/Object
#43 = Utf8 java/lang/Exception
#44 = Utf8 java/lang/System
#45 = Utf8 out
#46 = Utf8 Ljava/io/PrintStream;
#47 = Utf8 java/io/PrintStream
#48 = Utf8 println
#49 = Utf8 (Ljava/lang/String;)V
#50 = Utf8 getClass
#51 = Utf8 ()Ljava/lang/Class;
{
private java.lang.String song;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
public basic.object.A();
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: ldc #2 // String 你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!
7: putfield #3 // Field song:Ljava/lang/String;
10: return
LineNumberTable:
line 3: 0
line 6: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lbasic/object/A;
public void rap();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field song:Ljava/lang/String;
7: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 9: 0
line 10: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lbasic/object/A;
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #6 // class basic/object/A
3: dup
4: invokespecial #7 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #8 // Method rap:()V
12: aload_1
13: invokevirtual #9 // Method java/lang/Object.getClass:()Ljava/lang/Class;
16: pop
17: goto 17
LineNumberTable:
line 13: 0
line 14: 8
line 15: 12
line 16: 17
LocalVariableTable:
Start Length Slot Name Signature
0 20 0 args [Ljava/lang/String;
8 12 1 obj Lbasic/object/A;
StackMapTable: number_of_entries = 1
frame_type = 252 /* append */
offset_delta = 17
locals = [ class basic/object/A ]
Exceptions:
throws java.lang.Exception
}
SourceFile: "A.java"
JHSDB (java hotspot debug)
JHSDB (java hotspot debug),是一个功能非常强大的 Java 故障诊断和性能分析工具,要注意的是,程序与hsdb 要在同一版本的jdk环境中运行,而在 jdk8 没有 hsdb 工具,我电脑上安装了 openjdk 11,17,21 都有hsdb工具,本文这用的是 openjdk21 环境,终端执行 jhsdb hsdb
命令打开hsdb 页面,执行jps
查看pid
,再连接上进程。

File -> Attach to HotSpot process
Tools 中有很多实用的工具
Class Browser
查看类信息
public class basic.object.A @0x000000c801003000
主要维护有父类指针
(Super Class),属性
(Fields),方法
(Methods),常量池指针
(Constant Pool)
- Super Class
public class java.lang.Object @0x000000c800000e70
-
Fields
private java.lang.String song; (offset = 12)
-
Methods
- public void <init>() @0x0000000130408ca8;
- public static void main(java.lang.String[]) @0x0000000130408e28;
- public void rap() @0x0000000130408d58;
-
Constant Pool
Constant Pool of [public class basic.object.A @0x000000c801003000] @0x0000000130408a10
Class Hierarchy of public class basic.object.A @0x000000c801003000
public class basic.object.A @0x000000c801003000
public class java.lang.Object @0x000000c800000e70
A() 构造方法
public void \<init\>() @0x0000000130408ca8

Bytecode:
java
0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
4 aload_0
5 ldc #2 <你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!>
7 putfield #3 <basic/object/A.song : Ljava/lang/String;>
10 return
main() 方法
public static void main(java.lang.String[]) @0x0000000130408e28

Checked Exception(s) :
public class java.lang.Exception @0x000000c80000bf58
Bytecode
java
0 new #6 <basic/object/A>
3 dup
4 invokespecial #7 <basic/object/A.<init> : ()V>
7 astore_1
8 aload_1
9 invokevirtual #8 <basic/object/A.rap : ()V>
12 aload_1
13 invokevirtual #9 <java/lang/Object.getClass : ()Ljava/lang/Class;>
16 pop
17 goto 17 (0)
rap() 方法
public void rap() @0x0000000130408d58

Bytecode
java
0 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
3 aload_0
4 getfield #3 <basic/object/A.song : Ljava/lang/String;>
7 invokevirtual #5 <java/io/PrintStream.println : (Ljava/lang/String;)V>
10 return
常量池
常量池存储情况:
常量池完整信息
Index | Constant Type | Constant Value |
---|---|---|
1 | JVM_CONSTANT_Methodref | #10 #32 |
2 | JVM_CONSTANT_String | "你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!" |
3 | JVM_CONSTANT_Fieldref | #6 #34 |
4 | JVM_CONSTANT_Fieldref | #35 #36 |
5 | JVM_CONSTANT_Methodref | #37 #38 |
6 | JVM_CONSTANT_Class | public class basic.object.A @0x000000c801003000 (klass=0x000000c801003000) |
7 | JVM_CONSTANT_Methodref | #6 #32 |
8 | JVM_CONSTANT_Methodref | #6 #40 |
9 | JVM_CONSTANT_Methodref | #10 #41 |
10 | JVM_CONSTANT_Class | public class java.lang.Object @0x000000c800000e70 (klass=0x000000c800000e70) |
11 | JVM_CONSTANT_Utf8 | "song" |
12 | JVM_CONSTANT_Utf8 | "Ljava/lang/String;" |
13 | JVM_CONSTANT_Utf8 | "<init>" |
14 | JVM_CONSTANT_Utf8 | "()V" |
15 | JVM_CONSTANT_Utf8 | "Code" |
16 | JVM_CONSTANT_Utf8 | "LineNumberTable" |
17 | JVM_CONSTANT_Utf8 | "LocalVariableTable" |
18 | JVM_CONSTANT_Utf8 | "this" |
19 | JVM_CONSTANT_Utf8 | "Lbasic/object/A;" |
20 | JVM_CONSTANT_Utf8 | "rap" |
21 | JVM_CONSTANT_Utf8 | "main" |
22 | JVM_CONSTANT_Utf8 | "([Ljava/lang/String;)V" |
23 | JVM_CONSTANT_Utf8 | "args" |
24 | JVM_CONSTANT_Utf8 | "[Ljava/lang/String;" |
25 | JVM_CONSTANT_Utf8 | "obj" |
26 | JVM_CONSTANT_Utf8 | "StackMapTable" |
27 | JVM_CONSTANT_UnresolvedClass | basic/object/A |
28 | JVM_CONSTANT_Utf8 | "Exceptions" |
29 | JVM_CONSTANT_Class | public class java.lang.Exception @0x000000c80000bf58 (klass=0x000000c80000bf58) |
30 | JVM_CONSTANT_Utf8 | "SourceFile" |
31 | JVM_CONSTANT_Utf8 | "A.java" |
32 | JVM_CONSTANT_NameAndType | #13 #14 |
33 | JVM_CONSTANT_Utf8 | "你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!" |
34 | JVM_CONSTANT_NameAndType | #11 #12 |
35 | JVM_CONSTANT_Class | public final class java.lang.System @0x000000c800002bf8(klass=0x000000c800002bf8) |
36 | JVM_CONSTANT_NameAndType | #45 #46 |
37 | JVM_CONSTANT_Class | public class java.io.PrintStream @0x000000c80000a360 (klass=0x000000c80000a360) |
38 | JVM_CONSTANT_NameAndType | #48 #49 |
39 | JVM_CONSTANT_Utf8 | "basic/object/A" |
40 | JVM_CONSTANT_NameAndType | #20 #14 |
41 | JVM_CONSTANT_NameAndType | #50 #51 |
42 | JVM_CONSTANT_Utf8 | "java/lang/Object" |
43 | JVM_CONSTANT_Utf8 | "java/lang/Exception" |
44 | JVM_CONSTANT_Utf8 | "java/lang/System" |
45 | JVM_CONSTANT_Utf8 | "out" |
46 | JVM_CONSTANT_Utf8 | "Ljava/io/PrintStream;" |
47 | JVM_CONSTANT_Utf8 | "java/io/PrintStream" |
48 | JVM_CONSTANT_Utf8 | "println" |
49 | JVM_CONSTANT_Utf8 | "(Ljava/lang/String;)V" |
50 | JVM_CONSTANT_Utf8 | "getClass" |
51 | JVM_CONSTANT_Utf8 | "()Ljava/lang/Class;" |
对象内存信息
Object Histogram -> Inspect


虚拟机栈
这里把程序变一下,来查看虚拟机栈的信息情况
java
public static void main(String[] args) throws Exception {
A obj = new A();
while (true) {
obj.rap();
obj.getClass();
}
}
hsdb 查看main线程栈信息
上图可以看出,虚拟机栈其实存的都是方法帧
:
栈底是 main()
-> 接着是rap()
-> 接着是 println()
-> writeln
-> ... ->java.io.FileOutputStream.write(byte[], int, int)
-> 栈顶为 private native void writeBytes(byte[], int, int, boolean)
最终调到的是本地方法栈中的 native void writeBytes
方法。
对应栈内存里面也可以看出:
