JVM深入研究--JHSDB (jvm 分析工具)

文章目录

当你迷茫的时候,请点击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

  1. 前 4 字节:CA FE BA BE(魔数)

    这是 Java class 文件的标志性开头,称为 "魔数"(Magic Number)

    作用:用于 JVM 快速识别是否为有效的 class 文件

    十六进制 "CAFEBABE" 对应英文 "咖啡宝贝",是 Java 语言的标志性设计

  2. 接下来 4 字节:0000 0034(版本号)

    前 2 字节(0000):minor_version(次版本号)= 0

    后 2 字节(0034):major_version(主版本号)= 52(十六进制 34 转换为十进制是 52)

    版本对应关系:Java 8 的主版本号是 52,因此这个 class 文件是由 Java 8 编译产生的

  3. 接下来 2 字节:0034(常量池长度)

    表示常量池中有 52 项(34 十六进制 = 52 十进制)

    注意:常量池索引从 1 开始,因此实际有效索引范围是 1~52,共 52 项

  4. 剩余 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

    1. public void <init>() @0x0000000130408ca8;
    2. public static void main(java.lang.String[]) @0x0000000130408e28;
    3. 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 方法。

对应栈内存里面也可以看出:

相关推荐
majunssz2 小时前
深入剖析Spring Boot依赖注入顺序:从原理到实战
java·数据库·spring boot
乐之者v2 小时前
使用 Lens连接阿里云k8s集群
java·阿里云·kubernetes
南棱笑笑生2 小时前
20250931在RK3399的Buildroot【linux-6.1】下关闭camera_engine_rkisp
开发语言·后端·scala·rockchip
christine-rr3 小时前
【25软考网工】第五章(11)【补充】网络互联设备
开发语言·网络·计算机网络·php·网络工程师·软考
迎風吹頭髮3 小时前
UNIX下C语言编程与实践16-UNIX 磁盘空间划分:引导块、超级块、i 节点区、数据区的功能解析
java·c语言·unix
程序员小假3 小时前
线程池执行过程中遇到异常该怎么办?
java·后端
稚辉君.MCA_P8_Java3 小时前
DeepSeek Java 单例模式详解
java·spring boot·微服务·单例模式·kubernetes
洛_尘3 小时前
数据结构--4:栈和队列
java·数据结构·算法
疯癫的老码农3 小时前
【小白入门docker】创建Spring Boot Hello World应用制作Docker镜像并运行
java·spring boot·分布式·docker·微服务