如何判断对象存活?可达性?引用计数?

在介绍垃圾回收之前,首先需要解决的问题就是:如何判断对象存活?
因为垃圾回收,即内存回收,必然是对不再使用对象的内存区域进行回收,主要有两种存活方式:引用计数与可达性分析。

引用计数

在对象中添加一个引用计数器,一旦出现一个新的reference引用了它,则加一,不引用,则减一。
因此可以将引用计数为0的对象视为不再被使用。但这种方法有个问题,即相互引用。
这种方法很难解决对象之间相互循环引用的问题。比如下面的代码,objA 和 objB 互相引用,这种情况
下,引用计数器的值都是1,不会被垃圾回收。
image.png

可达性分析*

通过GC Root对象为起点,从这些节点向下搜索,搜索所走过的路径叫引用链,当一个对象到GC Root没有任何的引用链相连时,说明这个对象是不可用的。
(DFS或BFS?)
那么谁能作为可达性分析的GC Root呢
1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
2. 本地方法栈中JNI(Native方法)引用的对象
3. 方法区中类静态属性引用的对象
4. 方法区中常量引用的对象
5. 所有被同步锁(synchronized关键字)持有的对象。

强引用、弱引用、软引用、虚引用

强引用:垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错
误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
弱引用:在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管
当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不
一定会很快发现那些只具有弱引用的对象。
虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一
样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出等问题的产生。

Minor GC与Full GC

Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速
度一般也会比较快。
Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比
Minor GC 慢很多。

垃圾回收算法

标记清除法:先遍历标记,统一删除;
复制清除法,即半区复制法;
将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存
使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。 特点:实现简单,运行高效,但可用内存缩小为了原来的一半,浪费空
标记整理法:标记之后不删除,直接往右边移动,清理掉边界之外的内存;
分类收集法:根据所用内存区(新生代、老年代)来进行方法的选择。

垃圾回收器的优劣

收集器 串行、并
行or并发
新生
代/老
年代
算法 目标 适用场景
Serial 串行 新生代 复制算法 响应速
度优先
单CPU环境下的Client模式
ParNew 并行 新生代 复制算法 响应速
度优先
多CPU环境时在Server模式
下与CMS配合
Parallel
Scavenge
并行 新生代 复制算法 吞吐量
优先
在后台运算而不需要太多交
互的任务
Serial Old 串行 老年代 标记-整理 响应速
度优先
单CPU环境下的Client模
式、CMS的后备预案
Parallel
Old
并行 老年代 标记-整理 吞吐量 优先 在后台运算而不需要太多交 互的任务
CMS 并发 老年代 标记-清除 响应速
度优先
集中在互联网站或B/S系统
服务端上的Java应用
G1 并发 both 标记-整理
+复制算法
响应速
度优先
面向服务端应用,将来替换
CMS

CMS 收集器
Concurrent Mark Sweep 并发标记清除,目的是获取最短应用停顿时间。第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程基本上同时工作。在并发标记和并发清除阶段,虽然用户线程没有被暂停,但是由于垃圾收集器线程占用了一部分系统资源,应用程序的吞吐量会降低
CMS 垃圾回收基于标记清除算法实现,整个过程分为四个步骤:
初始标记: stw暂停所有的其他线程,记录直接与 gc root 直接相连的对象,速度很快 。
并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象,耗时较长,但是不需要停顿用户线程。
重新标记: 在并发标记期间对象的引用关系可能会变化,需要重新进行标记。此阶段也会stw,停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。
并发清除:清除死亡对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
image.png