1、概述
垃圾回收的三个问题:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
在运行时数据区我们有了解到内存区域分为线程共享的线程独立的,程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,所以当方法结束或者线程结束时,内存自然会回收。
Java堆和方法区这两个区域有着不确定性,这部分内存的分配和回收时动态的,垃圾回收期所关注的也是这部分内存该怎么管理。
2、哪些内存需要回收
2.1、对象已死?
垃圾回收器在对堆回收之前就要确定哪些内存需要回收,哪些内存需要被回收要判断在此内存上的对象时候已经死亡,死亡的对象不可能再被使用。
2.1.1、引用计数法
引用计数法是再对象中添加一个引用计数器,每当有一个地方引用它时,计数器加一,当引用失效时,计数器减一。当计数器为0时,对象被视为死亡。
好处:原理简单、判定效率高。
坏处:很难解决一些特殊情况,比如相互引用的情况,两个对象相互引用,但又不被其他对象引用。
2.1.2、可达性分析算法
可达性分析算法:通过定义的一些GC Roots作为对象的起始节点集,根据引用关系向下搜索,搜索过的路径成为“引用链”,如果一个对象到GC Roots之间没有任何引用链相连,对象被视为死亡。
GC Roots:
- 虚拟机栈中的引用对象,线程中使用到的参数、局部变量、临时变量
- 方法区中类静态属性引用的对象,引用类型静态变量
- 方法区中常量引用的对象,字符串常量池的引用
- 本地方法栈引用的对象
- Java虚拟机内部的引用
- 被synchronized持有的对象
- 其他
2.1.3、引用
在JDK1.2之后,Java对引用的概念进行了扩充,引用分为4种:
- 强引用:传统的引用概念,最常见的引用赋值,只要强引用还在就不会被回收
- 软引用:还有用但非必须的对象,在系统要发生OOM之前,会回收这部分内存
- 弱引用:非必须的对象,比软引用更弱一点,下次垃圾回收就会被回收
- 虚引用:最弱的一种引用关系,只是为了能在这个对象被回收时收到一个系统通知
3、垃圾回收算法
3.1、分代收集理论
弱分代假说:绝大多数对象都是朝生夕灭的
强分代假说:熬过越多次垃圾回收器过程的对象就越难以消亡。
Java堆之所以分为新生代、年轻代、老年代,就是因为分代收集理论,每个区域所适用的垃圾回收算法也有所不同。
部分收集:目标不是收集整个Java堆,部分收集又分为:
新生代收集(Minor GC/Young GC):只收集新生代的垃圾。
老年代收集(Major GC/Old GC):只收集老年代的垃圾。目前只有CMS收集器会有单独的老年代收集行为。
混合收集(Mixed GC):收集整个新生代和部分老年大的垃圾,目前只有G1会有这种行为
整堆收集(Full GC)收集整个Java堆和方法区的垃圾。
3.2、标记-清除算法
最早出现,也是最基础的垃圾回收算法,分为标记和清除两个阶段,首先标记出所有需要回收的对象,然后统一回收被标记的对象。后续很多收集算法都是以标记-清除算法为基础。
缺点:
- 执行效率不稳定,执行效率取决于要回收对象的数量,如果大部分需要回收,就必须进行大量的标记和清除动作
- 内存空间会出现碎片化。
3.3、标记-复制算法
标记复制算法解决了回收效率不稳定的问题,将可用的内存划分为大小相等的两块,当垃圾回收时,把存活的对象复制到另一半,这一半上的内存全部回收。
缺点:空间浪费太多。

3.4、标记-整理算法
标记整理算法标记过程和标记复制是一样的,但后续不对可回收对象直接清理,而是让所有存活的对象移动到内存空间的一端。
缺点:Stop The World,必须暂停用户应用来移动存活对象。
4、 HotSpot的算法细节实现
4.1、根节点枚举
第二节说到了可达性分析算法,可达性分析首先需要先找到GC Root,根节点枚举就是找GC Root。根节点枚举必须事在一个能保障一致性的快照中执行,所以一定会出现停顿所有用户线程的情况。
4.2、安全点
在OopMap的协助下,HotSpot可以快速准确地完成GC Roots枚举,但一个很现实的问题随之而 来:可能导致引用关系变化。所以HotSpot只是在“特定的位置”记录OopMap。这些位置被称为安全点。
如何在垃圾收集发生时让所有线程(这里其实不包括执行JNI调用的线程)都跑到最近的安全点,然后停顿下来?
- 抢先式中断:抢先式中断不需要线程的执行代码主动去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程行,让它一会再重新中断,直到跑到安全点上。现在几乎没有虚拟机实现采用抢先式中断来暂停线程响应GC事件。
- 主动式中断:当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最
近的安全点上主动中断挂起。
4.3、安全区域
安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任 意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。
4.4、记忆集与卡表
-
字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个 精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
-
对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。
-
卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。
4.5、写屏障
在HotSpot虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的。
4.6、并发可达性分析
三色标记法:
-
白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
-
黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
-
灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。
图中下面两种情况,会出现“对象消失”的问题,在这条链上还没被扫描过的对象被切断了,然后又引用到了黑色对象,因为黑色对象已经扫描过了,不会再重新扫描。所以那个对象就一直是白色的了。
产生“对象消失的必要条件”:
-
赋值器插入了一条或多条从黑色对象到白色对象的新引用
-
赋值器删除了全部从灰色对象到该白色对象的直接或间接引用
只要破坏其中一个条件即可:增量更新、原始快照