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