详解Java的 GCRoot

2 分钟阅读

GCRoot是什么?

GCRoot是Java实现精准式GC算法的基石。在了解GCRoot之前就必须对 GC、可达性分析、精准式GC的概念有所了解。

GC是垃圾回收的简称。是Java对堆栈进行内存管理的重要手段。将开发者的内存管理权限收归Jvm统一托管,降低开发者维护内存的心智成本,提高开发进度,降低技术开发者的上手门槛。

GC算法从提出方案到成为正式生产特性,不是一蹴而就的,完善的GC算法都是经历过长久的版本孵化、功能迭代和一些列的优化提案,并逐步的新增、暴露GC实现细节的用户自定义配置项,以便于用户去在一定范围内根据自身的实际业务需求进行调整,找到自身应用的GC和内存管理开销的平衡点。Jvm也会给GC算法提供一些列的缺省参数,简化用户的配置。

精准式GC是指通过算法精准区别识别对象指针,精准区分垃圾和存活对象的GC算法。可达性分析算法是GC中用于快速区分垃圾的算法,而GCRoot是可达性分析的根节点,是算法的初始点。

GCRoot有哪些?

在OpenJDK的源码中搜索GC_ROOT。可以在Hprof工具相关的代码中查到 HeapHprofBinWriter, 列举9种类型GCRoot, 包含8种类型未知类型(为了兼容其他JVM实现的Dump),相关源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
    
    // hprof binary file header
    private static final String HPROF_HEADER_1_0_2 = "JAVA PROFILE 1.0.2";
    //    .....
    private static final int HPROF_GC_ROOT_UNKNOWN       = 0xFF;
    private static final int HPROF_GC_ROOT_JNI_GLOBAL    = 0x01;
    private static final int HPROF_GC_ROOT_JNI_LOCAL     = 0x02;
    private static final int HPROF_GC_ROOT_JAVA_FRAME    = 0x03;
    private static final int HPROF_GC_ROOT_NATIVE_STACK  = 0x04;
    private static final int HPROF_GC_ROOT_STICKY_CLASS  = 0x05;
    private static final int HPROF_GC_ROOT_THREAD_BLOCK  = 0x06;
    private static final int HPROF_GC_ROOT_MONITOR_USED  = 0x07;
    private static final int HPROF_GC_ROOT_THREAD_OBJ    = 0x08;
    //    .....
}

Hprof 是Jvm堆内存快照文件. 通过jmap、jconsole等工具将内存快照保存下来,有助于开发中排查内存泄露等问题。

源码中定义的Hprof版本标识为JAVA PROFILE 1.0.2,这个很重要,后续还会在提到。

很多博客和文章中会提到Eclipse的内存分析工具Eclipse Memory Analyzer(MAT),MAT的文档中描述的GCRoot的类型和OpenJDK中的不一致,多了6个枚举 (INTERNED_STRING、FINALIZING、DEBUGGER、REFERENCE_CLEANUP、VM_INTERNAL、JNI_MONITOR)。

怎么会出现不一致的情况呢? 多方查找资料,也没有得到很明确的解答,甚至可以说没有找到任何相关的回答,就好像全世界除了我没有人对此有疑问一样。一头莫展之际,灵光一闪的想到VisualVM这个堆dump分析工具。

VisualVM的Commit解惑

VisualVM 1.4.4是最后一个集成在JDK中的版本,支持JDK 1.8及以下版本。 VisualVM 2.x支持JDK 1.8以上的版本。不再集成在JDK中,作为独立开源项目发布和使用

在历史提交记录的备注中查到端倪,OpenJDK和现行的JVM基本上都是遵循JAVA PROFILE 1.0.2规范,Android操作系统中使用的是Dalvik虚拟机遵循JAVA PROFILE 1.0.3

2.x版本支持Android的Dalvik虚拟机的内快照存堆Dump解析,VisualVM升级支持JAVA PROFILE 1.0.3并支持新增的6个GCRoot类型。

早期由于MAT和VisualVM等分析查看工具,不支持1.0.3规范,会使用hprof-conv input.hprof out.hprof将dump文件转换为1.0.2规范格式。

以下截取VisualVM 2.1.4版本的GCRoot.java部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public interface GCRoot {
    //~ Static fields/initializers -----------------------------------------------------------------------------------------------

    /**
     * JNI global GC root kind.
     */
    public static final String JNI_GLOBAL = "JNI global"; // NOI18N

    /**
     * JNI local GC root kind.
     */
    public static final String JNI_LOCAL = "JNI local"; // NOI18N

    /**
     * Java frame GC root kind.
     */
    public static final String JAVA_FRAME = "Java frame"; // NOI18N

    /**
     * Native stack GC root kind.
     */
    public static final String NATIVE_STACK = "native stack"; // NOI18N

    /**
     * Sticky class GC root kind.
     */
    public static final String STICKY_CLASS = "sticky class"; // NOI18N

    /**
     * Thread block GC root kind.
     */
    public static final String THREAD_BLOCK = "thread block"; // NOI18N

    /**
     * Monitor used GC root kind.
     */
    public static final String MONITOR_USED = "monitor used"; // NOI18N

    /**
     * Thread object GC root kind.
     */
    public static final String THREAD_OBJECT = "thread object"; // NOI18N

    /**
     * Unknown GC root kind.
     */
    public static final String UNKNOWN = "unknown"; // NOI18N

    //    ================================
    //    以下为HPROF HEAP 1.0.3新增 
    //    ================================

    /**
     * Interned string GC root kind.
     */
    public static final String INTERNED_STRING = "interned string"; // NOI18N

    /**
     * Finalizing GC root kind.
     */
    public static final String FINALIZING = "finalizing"; // NOI18N

    /**
     * Debugger GC root kind.
     */
    public static final String DEBUGGER = "debugger"; // NOI18N

    /**
     * Reference cleanup GC root kind.
     */
    public static final String REFERENCE_CLEANUP = "reference cleanup"; // NOI18N

    /**
     * VM internal GC root kind.
     */
    public static final String VM_INTERNAL = "VM internal"; // NOI18N

    /**
     * JNI monitor GC root kind.
     */
    public static final String JNI_MONITOR = "JNI monitor"; // NOI18N
}

VisualVM GCRoot详解

GC Root 描述  
JNI_GLOBAL native本地方法代码中的全局变量, JNI或者Jvm内部方法  
JNI_LOCAL native本地方法代码中的本地变量, JNI或者Jvm内部方法  
JAVA_FRAME Jvm虚拟机栈帧中引用对象。线程调用的方法参数,局部变量等等  
NATIVE_STACK native本地方法栈中引用对象, JNI或者Jvm内部方法。文件/网络/IO相关的低层方法或反射  
STICKY_CLASS bootstrap/system类加载器加载的类。例如:jr.jar中java.util包下类  
THREAD_BLOCK An object that was referenced from an active thread block.  
MONITOR_USED 调用wait()或notify()方法的对象,synchronized关键字监控的对象  
THREAD_OBJ 调用start()启动之后未停止的线程对象  
UNKNOWN 未知类型, 不同平台的Dump文件输出的信息可能会不同  
INTERNED_STRING    
FINALIZING    
DEBUGGER    
REFERENCE_CLEANUP    
VM_INTERNAL    
JNI_MONITOR    

总结一下,JNI相关(本地/全局/引用变量), 虚拟机相关(运行时的类对象,静态对象,常量池),线程相关(对象,栈帧,同步) 都可以作为GC Root。

参考资料

  1. Eclipse Memory Analyzer
  2. OpenJDK HprofReader
  3. Oracle Visualvm
  4. HPROF Agent
  5. hprof manual

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 TinyZ Zzh (包含链接: https://tinyzzh.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。 如有任何疑问,请 与我联系 (tinyzzh815@gmail.com)

TinyZ Zzh

TinyZ Zzh

专注于高并发服务器、网络游戏相关(Java、PHP、Unity3D、Unreal Engine等)技术,热爱游戏事业, 正在努力实现自我价值当中。

评论

  点击开始评论...