大家都知道,JVM 有垃圾回收的机制,垃圾回收的前提是要知道:什么是垃圾!然后再是如何识别垃圾!
什么是垃圾
垃圾,本质上就是没有引用的对象(们),下面来介绍两种垃圾
1. 没有引用指向的对象
下图是对象间引用的状态,从正常引用到引用断开,这个 A 和 C 的引用断开之后,C 就成了那个垃圾。
2. 没有引用指向的一组对象
一个典型的案例如下图,就是循环引用,这几个对象看起来都有引用指向,但是其实他们只是一堆紧紧相拥的垃圾。
如何识别垃圾
上面介绍了什么是垃圾,那要如何才能识别出垃圾呢?主要有两种算法:
- 引用计数法
- 可达性分析法
1. 引用计数法
算法很简单,就是在对象头上加上被引用的次数,对象的被引用的次数为 0 之日,就是其成为垃圾之时!这个算法的优点是垃圾回收及时,只要对象被引用次数为 0,就可以回收了。
下图是引用计数的示意图,对象 A、B、C 都被引用了一次
如果 A 跟 C 的引用断开,则 C 的引用次数减一,变为 0,此时 C 就是垃圾
引用计数法有个致命的缺点:那就是无法识别出循环引用!
下图是一个循环引用,明明他们就是一堆垃圾,但是因为被引用次数都不为 0,引用计数法无法识别出他们是垃圾。
2. 可达性分析法
引用计数法的缺点过于致命,目前 JVM 采用的是另一种算法来识别垃圾:可达性分析法。
这个算法的基本思路就是:从一系列根对象(GC Roots
)开始,根据引用关系向下搜索,如果某个对象到 GC Roots
间没有任何引用,则证明此对象是不可能再被使用的,也就是垃圾。
其示意图如下,左边绿色部分的对象,都可以连向 GC Roots
,所以他们都是存活的对象。而右边灰色的部分,即使他们是循环引用,他们也跟 GC Roots
之间没有连接路径,所以灰色部分的对象是垃圾。
那么,究竟是哪些对象能成为至高无上的 GC Roots
呢?以下是主要的 GC Roots
:
- 虚拟机栈中引用的对象,如各个线程调用的方法堆栈中的参数、局部变量等。
- 方法区中类的静态属性引用的对象,如类的引用类型的静态变量。
- 方法区中常量引用的对象,如字符串常量池里的引用。
- 本地方法栈中 JNI(Native 方法)引用的对象。
- 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(比如
NullPointExcepiton
、OutOfMemoryError
)等,还有系统类加载器。
优点:解决引用计数器所不能解决的循环引用问题。
缺点:
- 耗时:因为需要从
GC Roots
开始逐个检查引用; - STW:GC 过程中需要保证对象的引用关系不能发生变化,所以 GC 进行时必须停顿所有执行线程(STW:Stop The World)。
总结
第一部分我们介绍了什么是垃圾:没有任何引用指向的一个或多个对象。
第二部分介绍了如何识别垃圾,有两种算法:
- 引用计数法:通过给对象添加被引用的次数来识别。优点是回收简单及时;缺点是无法解决循环引用。
- 可达性分析法:从一系列根对象(
GC Roots
)开始,根据引用关系向下搜索,如果某个对象到GC Roots
间没有任何引用,则此对象就是垃圾。优点是解决了循环引用;缺点是耗时和 STW。