本文从JVM如何断定对象是否须要回收开始分析,再到JVM的几种垃圾回收思想如何产生,最后再来介绍JVM经典的7种垃圾回收器的特色(不包含ZGC);算法
JVM根据对象存活周期不一样将heap划分红了新生代、老年代、永久代(方法区&元空间)。
有个问题,JVM是先有的分代思想而后根据不一样的代发展不一样的垃圾回收思想,仍是先有的垃圾回收思想才划分不一样的代?
多线程
JAVA与C有个很显著的不一样,就是JAVA不须要手动归还内存,彻底由GC自动管理内存回收。那么GC是如何判断对象是否须要回收的呢?并发
引用计数法性能
引用计数法是指在对象中添加一个引用计数器,若是被其余对象引用则计数器+1,引用失效时-1。
优势:实现简单,判断效率也很高;
缺点:存在对象循环引用问题,因此在主流的虚拟机中并无采用引用计数器。
对象A持有对象B的引用,对象B持有对象A的引用,除此以外在无其余对象引用A和B,GC没法回收这样的对象.
线程
可达性分析3d
在主流商用语言(JAVA/C#/Lisp)都是使用可达性分析算法来断定对象是否存活。主要思想就是经过一系列被称为GC Roots
的对象做为起始点开始先下搜索,走过的路径称为引用链,若是某个对象没有任何一条到达GC Roots
对象的引用链则表明此对象可回收的。
JAVA中能够被称为GC Roots
对象:code
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中的类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(即通常说的Native方法)中引用的对象;
GC Roots
没法到达的对象并非必定会被回收,一个对象至少要被标记两次才会真正死亡。
cdn
GC Roots
时会被第一次标记,并进行一次筛选,筛选的条件是该对象是否有必要执行finalize()
。finalize()
方法finalize()
方法finalize()
方法;finalize()
方法里关联上任何一条引用链,则会被移出即将要回收的集合,不然该对象真正“死亡”。在JVM知道那些对象是可回收的后,须要开始真正的回收对象了。JVM在发展的过程当中出现了几种经典的回收思想,这里不讨论每种算法具体如何实现(由于我也不了解...)。对象
复制算法
为了解决效率问题,出现了一种复制的算法,一开始是将内存按1:1划分红两块,每次只在其中一块内存上分配对象,当触发垃圾回收时将存活的对象所有复制到另外一块的内存上,而后把已经使用过的那快内存清空掉。这样既解决了效率问题也解决了内存碎片化的问题。但同时也带来了空间浪费的缺点:每次只能使用50%的空间。blog
实际状况并非每次回收时一块Survivor都能装下全部存活对象,那这时就会经过“空间分配担保”的机制直接晋升到老年代。
标记-整理算法
因为老年代的对象都是长期存活,因此复制算法并不适用老年代,所以又提出了“标记-整理”算法,标记过程与“标记-清除”算法同样,只是后续并非直接清除对象而是先将全部存活对象都向一端移动,而后直接清理掉边界之外的内存。
分代收集算法
当前主流商用垃圾回收器都是采用的“分代收集算法”,这个算法并无什么新的思想只是根据对象存活周期的不一样将内存划分红不一样的代而后采用不一样的回收算法。
黄色表明只处理新生代的GC,蓝色表明只处理老年代GC,各GC之间的连线表明能够搭配使用。G1能够独立回收整个head;
在介绍这些收集器各自的特性以前,让咱们先来明确一个观点:虽然咱们会对各个收集器进行比较,但并不是为了挑选一个最好的收集器出来,虽然垃圾收集器的技术在不断进步,但直到如今尚未最好的收集器出现,更加不存在“万能”的收集器,因此咱们选择的只是对具体应用最合适的收集器。若是有一种放之四海皆准、任何场景下都适用的完美收集器存在,HotSpot虚拟机彻底不必实现那么多种不一样的收集器了(摘选自《深刻理解Java虚拟机(第2版)》)。
这里说明一下"并行"和"并发"的概念。
- 并行(Parallel):多条垃圾收集线程并行工做,而此时用户线程仍处于等待状态。
- 并发(Concurrent):垃圾收集线程与用户线程同时执行(不必定是并行有多是交替执行),多核CPU的状况下不一样的线程在不一样的CPU上同时执行。
Serial
Serial收集器是最基本历史最悠久的收集器,JDK1.3.1以前是新生代惟一的选择。Serial是一个单线程收集器,这里的“单线程”并非指一个CPU或一条线程而是Serial在垃圾收集时必须暂停其余工做线程(Stop The World)也就是俗称的“STW”。
ParNew
ParNew收集器是Serial收集器的多线程版本,除使用多条线程进行垃圾收集以外,其他行为包括控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器彻底同样,在实现上,这两种收集器也共用了至关多的代码。
CMS
搭配使用,多核CPU状况下能有效利用系统资源。
Parallel Scavenge
Parallel Scavenge收集器是一个并行的多线程年轻代收集器,其余收集器关心如何缩短垃圾收集的时间而它关注的是如何控制系统运行的吞吐量(吞吐量(吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间))
)。高吞吐量能够高效率的利用CPU时间,尽快完成运算任务,只要适合在后台运算而不须要太多交互的任务。
Serial Old
Serial的老年代版本,它也是一款使用"标记-整理"算法的单线程的垃圾收集器,优劣和Serial同样。有两大用途:
Concurrent Mode Failure
状况下老年代预备方案
Parllel Old
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用"标记-整理"算法。JDK1.6才提供,在此以前Parallel Scavenge只能和单线程的Serial Old搭配使用,因为老年代的Serial Old在服务端拖累又不能有效利用多核CPU的处理能力,致使Parallel Scavenge的高吞吐名副其实。直到Parllel Old的出现“吞吐量优先”的收集器才有了用武之地,任何注重吞吐量以及CPU资源敏感的场合,均可以优先考虑Parallel Scavenge加Parallel Old收集器一块儿配合使用
CMS
真正意义上的一款具备划时代意义的垃圾收集器,基于“标记-清除”算法实现,关注点在获取最短停顿时间为目标,大量运用在B/S系统的服务端上。
整个回收过程分为四个步骤:
标记
GC Roots
能直接关联到的对象,速度很快。须要STW
标记
GC Roots
找到全部能关联到的对象
由于
并发标记
是和用户线程并发的因此在标记的过程当中会产生新的对象,因此要从新标记。须要STW
并发清除前面全部标记的对象。
G1
G1全称“Garbage First”垃圾收集器直至JDK7,Sun公司才认为G1达到足够成熟的商用程度,目标是在将来能够替换掉CMS。以前的GC都只负责整个新生代/老年代,而G1能够独立负责整个Heap,G1是将整个Heap划分红多个大小相等的Region,逻辑上仍保留分代的概念,但已不是物理分隔了,它们都是一部分不须要连续的Region集合。
G1有如下特色:
指定一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不超过N毫秒
)。G1对每个Region的垃圾堆积的价值大小维护了一个优先列表,每次根据容许的收集时间,优先回收价值大的Region(这就是Garbage First名称的由来),保证了有限的时间内获取尽量高的收集效率。G1收集器的大概步骤:
前三个步骤与CMS运做过程大体类似,