垃圾回收算法
引用计数法(java中并没有使用引用计数算法):给每个创建的对象添加一个引用计数器,每当此对象被某个地方引用时,计数值+1, 引用失效时-1,所以当计数值为0时表示对象已经不能被使用。引用计数算法大多数情况下是个比较不错的算法, 简单直接,也有一些著名的应用案例但是对于Java虚拟机来说,并不是一个好的选择,因为它很难解决对象直接相互循环引用的问题。
优点:实现简单,执行效率高,很好的和程序交织。
缺点: 无法检测出循环引用。
可达性分析算法:通过一系列的“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达” ,即该对象是不可用的。
可以当作:
栈帧中的局部变量表中的reference引用所引用的对象
方法区中static静态引用的对象
方法区中final常量引用的对象
本地方法栈中JNI(Native方法)引用的对象
Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象(比如 NullPointExcepiton、 OutOfMemoryError) 等,还有系统类加载器。
所有被同步锁(synchronized关键字) 持有的对象。
四种引用类型
强引用(Strong Reference):强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。
软引用(Soft Reference):如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。 软引用可以和一个引用队列 (ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(Weak Reference):用来描述那些非必须对象, 但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。 当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。 在JDK 1.2版之后提供了WeakReference类来实现弱引用。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用 所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(Phantom Reference):是最弱的一种引用关系。如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于 :
1虚引用必须和引用队列 (ReferenceQueue)联合使用。
2当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之
前,把这个虚引用加入到与之关联的引用队列中。
标记-清除算法
分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
标记-清除算法有两个不足之处:
当大量对象需要回收时,必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低。
内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
实现:CMS垃圾收集器
标记-复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
缺点:
1)需要提前预留一半的内存区域用来存放存活的对象(经过垃圾收集后还存活的对象),这
样导致可用的对象区域减小一半,总体的GC更加频繁了
2)如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
3)如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的
实现:新生代Serial 、 新生代ParNew、Parallel Scavenge
标记-整理算法
在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法
实现:G1收集器(基于标价-整理算法)
Serial收集器
单线程收集器,“单线程”的意义不仅仅说明它只会使用一个CPU或一个收集线程去完成垃圾收集工作; 更重要的是它在垃圾收集的时候,必须暂停其他工作线程,直到垃圾收集完毕;
ParNew 收集器
ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之 外,其余的行为包括Serial收集器可用的所有控制参数 、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致
ParNew收集器在单CPU服务器上的垃圾收集效率绝对不会比Serial收集器高;
但是在多CPU服务器上,效果会明显比Serial好
Parallel Scavenge收集器
又称为吞吐量优先收集器,和ParNew收集器类似,是一个新生代收集器 。使用复制算法的并行多线程收集器。 Parallel Scavenge是Java1.8默认的收集器,特点是并行的多线程回收,以吞吐量优先。
Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(虚拟机总共运行100分钟,垃圾收集时间为1分钟,那么吞吐量就是99%)
自适应调节策略,自动指定年轻代、Eden、Suvisor区的比例。
Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。Parallel Old是Java1.8默认的收集器
CMS 收集器
CMS(concurrent mark sweep)是以获取最短垃圾收集停顿时间为目标的收集器,CMS收集器的关注点尽可能缩短垃圾收集时用户线程的停顿时间,停顿时间越短就越适合与用户交互的程序,使用的算法是标记-清除算法实现
整个过程分4个步骤:
1) 初始标记
2) 并发标记
3) 重新标记
4) 并发清除
其中 初始标记 和 重新标记 都需要stopTheWorld
缺点:
CMS收集器对CPU资源非常敏感(在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用 了一部分线程而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数是(处理器核心数量 +3) /4,也就是说,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的 处理器运算资源,并且会随着处理器核心数量的增加而下降。但是当处理器核心数量不足四个时,CMS对用户程序的影响就可能变得很大。如果应用本来的处理器负载就很高, 还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然大幅降低)。
CMS收集器无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生。(由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为"浮动垃圾"。同样也是由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此CMS收集器不能像其他收集器那样等待 到老年代几乎完全被填满了再进行收集,必须预留一部分空间供 并发收集时的程序运作使用。)
空间碎片:CMS是一款基于标记-清除算法实现的收集器,所有会有空间碎片的现象。
G1收集器
Garbage First是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。
特点
-
G1把内存划分为多个独立的区域Region
-
G1仍然保留分代思想,保留了新生代和老年代,但他们不再是物理隔离,而是一部分Region的集合
-
G1能够充分利用多CPU、多核环境硬件优势,尽量缩短STW
-
G1整体整体采用标记整理算法,局部是采用复制算法,不会产生内存碎片
-
G1的停顿可预测,能够明确指定在一个时间段内,消耗在垃圾收集上的时间不超过设置时间
-
G1跟踪各个Region里面垃圾的价值大小,会维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限事件内高效的收集垃圾
G1提供了两种GC模式,Young GC和Mixed GC,两种均是完全Stop The World的。
Young GC:选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC 的时间开销。
Mixed GC:选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代 Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
四个阶段:
初始标记 :和CMS一样只标记GC Roots直接关联的对象
并发标记 :进行GC Roots Traceing过程
最终标记 :修正并发标记期间,因程序运行导致发生变化的那一部分对象
筛选回收 :根据时间来进行价值最大化收集