JVM深入研究--JHSDB (jvm 分析工具)
文章目录
- 前言
- A.java
- A.class
- javap 反编译 class
- JHSDB (java hotspot debug)
- 查看类信息
- A() 构造方法
- main() 方法
- rap() 方法
- 常量池
- 对象内存信息
- 虚拟机栈
当你迷茫的时候,请点击JVM 目录大纲 快速查看前面的技术文章,相信你总能找到前行的方向
前言
上一篇 JVM 深入研究 – 详解class 文件 结合官网文档分析 class 文件示例,作为 JVM 深入学习的开篇,今天来通过工具来简化分析 class 的步骤
A.java
先上原代码,回顾一下故事的主角:
package basic.object;public class A {// 一段rapprivate 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
Classfile /path/to/java-oops/target/classes/basic/object/A.classLast modified 2025-9-26; size 969 bytesMD5 checksum 7b641ea8aee513d0afbf8492ea0fd19eCompiled from "A.java"
public class basic.object.Aminor version: 0major version: 52flags: 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_PRIVATEpublic basic.object.A();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: ldc #2 // String 你看这个面它又长又宽,还有这个碗它又大又圆,两者之间并没有关系,但我要用rap把它们缝在一起,耶!7: putfield #3 // Field song:Ljava/lang/String;10: returnLineNumberTable:line 3: 0line 6: 4LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lbasic/object/A;public void rap();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;3: aload_04: getfield #3 // Field song:Ljava/lang/String;7: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V10: returnLineNumberTable:line 9: 0line 10: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lbasic/object/A;public static void main(java.lang.String[]) throws java.lang.Exception;descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new #6 // class basic/object/A3: dup4: invokespecial #7 // Method "<init>":()V7: astore_18: aload_19: invokevirtual #8 // Method rap:()V12: aload_113: invokevirtual #9 // Method java/lang/Object.getClass:()Ljava/lang/Class;16: pop17: goto 17LineNumberTable:line 13: 0line 14: 8line 15: 12line 16: 17LocalVariableTable:Start Length Slot Name Signature0 20 0 args [Ljava/lang/String;8 12 1 obj Lbasic/object/A;StackMapTable: number_of_entries = 1frame_type = 252 /* append */offset_delta = 17locals = [ 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 @0x000000c801003000public class java.lang.Object @0x000000c800000e70
A() 构造方法
public void \<init\>() @0x0000000130408ca8
Bytecode:
0 aload_01 invokespecial #1 <java/lang/Object.<init> : ()V>4 aload_05 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
0 new #6 <basic/object/A>3 dup4 invokespecial #7 <basic/object/A.<init> : ()V>7 astore_18 aload_19 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
0 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>3 aload_04 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
虚拟机栈
这里把程序变一下,来查看虚拟机栈的信息情况
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
方法。
对应栈内存里面也可以看出: