前面有篇文章介绍了JVM的基本信息,而众所周知,Java带有垃圾收集机制,那本篇就介绍一下Java中的垃圾收集算法与垃圾收集器。html
朋友,再花20分钟了解下噻 (嘿嘿...)...算法
在Java开发中,开发者无需关注动态分配与垃圾回收,更加专一于开发事项,那么本篇就介绍一下关于GC (Garbage Collection)方面的一些内容:安全
此处提问一个问题:Java中程序计数器,本地方法栈等是线程私有的区域,随着线程的消失而消除,那么此处的垃圾收集是如何进行的呢?服务器
如何断定一个Java对象(堆中对象)已经成为垃圾对象,或对象已经死亡呢?有两种方式:数据结构
1.引用计数算法:经过判断对象的引用数量,决定对象是否能够被回收。多线程
给对象中添加一个引用计数器,每有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1;任什么时候刻计数器值为0表示对象不能再被使用。并发
这种算法的实现简单,效率高,但很难解决对象之间循环引用的问题。(Java中不采用此算法)性能
2.可达性分析算法:经过判断对象的引用链是否与根节点可达,决定对象是否能够被回收。测试
根搜索算法是经过一些“GC Roots”对象做为起点,从这些节点开始往下搜索,搜索经过的路径成为引用链(Reference Chain),当一个对象没有被GC Roots 的引用链链接的时候,说明这个对象是不可用的。优化
扩展:若是某一对象与GCRoots没有引用,则必定会进行回收吗?能够看一下对象的自我救赎。
那判断完对象是垃圾对象,按照什么样的方式(算法)对垃圾对象/死亡对象进行回收呢?
1.标记-清楚算法(Mark-Sweep)
该算法分为标记、清除两个阶段,先标记处全部须要回收的对象,在标记完成后统一回收全部被标记的对象。是最基础的收集算法,后续的收集算法都是基于这种思路并对其不足进行改进而获得的。
它存在两个不足:一是效率问题,由于标记和清楚的过程效率都不高,二是空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会致使之后在程序运行过程当中须要分配较大对象时,没法找到足够的内存而不得不提早触发另外一次垃圾收集动做。
2.复制算法(Copy)
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,好比新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂状况,只要移动堆顶指针,按顺序分配内存便可,实现简单,运行高效。
复制收集算法在对象存活率较高时就要进行较多的复制操做,效率将会变低。
3.标记-整理算法(Mark-Compact)
标记—整理算法和标记—清除算法同样,可是标记—整理算法不是把存活对象复制到另外一块内存,而是把存活对象往内存的一端移动,而后直接回收边界之外的内存。标记—整理算法提升了内存的利用率,而且它适合在收集对象存活时间较长的老年代。
注:标记整理算法与标记清除算法最显著的区别是:标记清除算法不进行对象的移动,而且仅对不存活的对象进行处理;而标记整理算法会将全部的存活对象移动到一端,并对不存活对象进行处理,所以其不会产生内存碎片。
分代收集算法
当前商用的虚拟机都采用的是 “分代收集” 算法,其实质上是前三个算法理论在JVM中的一个区域分布,至关于前面三个的综合。
不一样的对象的生命周期(存活状况)是不同的,而不一样生命周期的对象位于堆中不一样的区域,所以对堆内存不一样区域采用不一样的策略进行回收能够提升 JVM 的执行效率。通常把Java堆分为新生代和老年代。新生代中,每次垃圾收集都发现有大批对象死去,只有少许存活,就用复制算法;老年代中,对象存活率高、没有额外空间对它进行分配担保,就用“标记-清除”或“标记-整理”算法。
在JVM内存区域章节中,介绍了JVM的划分,而堆空间中分为了新生代、老年代等空间,每一个空间的特性决定其使用不一样的GC算法。
确认了垃圾对象,且了解了按照什么样的逻辑进行垃圾对象收集,那具体的是由什么来进行收集操做的呢?
在了解这个内容以前,先得有一个小的讨论,讨论几下几点:
1.既然每一个算法中有有标记相关的一个过程,那么在标记时,整个程序是并行的呢?仍是直接中止程序,再进行呢?
按照图上理论阶段的标记来说,若是程序再执行中进行垃圾对象标记,则其中会发生大几率的遗漏标记的发生,还有一个是若是某一GCRoot是垃圾对象呢?所以保证该阶段的一致性,此阶段是中止程序的。有一个浅显的例子:你妈打扫房间时,你在扔垃圾,那这种状况下,垃圾会确定会有遗漏的呐,估计孩子可能被打S吧...因此按照这个逻辑来说,此时在标记执行的时间点或时间段,程序犹如冻结通常,这个被称为STW事件(Stop The World),中止世界,意思是GC标记时须要停顿全部的Java执行线程。
STW缘由:由于可达性分析算法必须是在一个确保一致性的内存快照中进行。若是在分析的过程当中对象引用关系还在不断变化,分析结果的准确性就不能保证。
2.接上个问题,若是标记阶段程序停顿,那么何时,什么时间点停顿呢?如何选择该时间呢?
STW事件发生的是何时?这个就得从与GCRoots的引用链开始介绍了。
首先得清除哪些对象是GCRoots,这个有两种方式:一是遍历方法区和栈区查找(保守式 GC,消耗过高)。二是经过 OopMap 数据结构来记录 GC Roots 的位置(准确式 GC,且OopMap能够做为安全点,记录该位置可停顿)。
安全点的做用主要使程序再特定的位置能够停顿,进行GC标记。安全点意味着在这个点时,全部工做线程的状态是肯定的,JVM 就能够安全地执行 GC 。
那么停顿也有两种:一是抢先式中断,发生GC时,若程序没有停顿到安全点,则恢复并达到安全点时停顿,二是主动式中断,发生GC时,不直接操做线程中断,而是简单地设置一个标志,让各个线程执行时主动轮询这个标志,发现中断标志为真时就本身中断挂起。JVM 采起的就是主动式中断。轮询标志的地方和安全点是重合的。
安全点(safe Point)主要是针对于正在执行的线程来决定的,那么对于正在阻塞或休眠的线程呢?这个如何处理呢?
--- 若是线程处于中断状态,则未必会恰好停顿到安全点上,此时引入了一个安全区(Safe Region)的概念,这个表示在该区域内,GC的引用关系是不会发生变化的,任意时刻进行回收都是安全的。
这儿借助一下参考博客:http://www.javashuo.com/article/p-zfzzphhv-dk.html
http://wuzhangyang.com/2019/01/18/understand-safe-point/
3.同理,标记对象以后的清除阶段是并行进行的呢?仍是中止程序,再进行清除操做呢?这个清除的过程程序会如何进行呢?有什么具体的操做演示呢?
同理,按照现有的理论来说,若对象已经被标记为垃圾对象了,那么此时直接进行清除处理便可,这个无关程序是否停顿,随意啦...
并且清除是不是串行仍是并行,这个是与垃圾收集器相关的。
串行采用单线程处理,适用于单CPU或并发能力弱的系统,当回收器启动后会暂停工做线程,更因为是单线程,致使其STW的时间会很长
并行采用多线程处理,适用于多CPU或并发能力强的系统,当回收器启动后也会暂停其余工做线程,但因为是多线程,会减小其STW时间
------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------
垃圾收集器 是随着JDK版本的升级而不断的优化,这与JDK不断发展是息息相关的,所以下面介绍的就是JDK中目前已存在的垃圾收集器。
1.Serial 收集器
最早的收集器,单线程,采用复制算法,用于新生代,进行垃圾回收时,会产生STW,主要适用于单核CPU的场景,能够避免多线程上下文切换带来的消耗。单线程:只会使用一条垃圾收集线程去完成垃圾收集工做,更重要的是它在进行垃圾收集工做的时候必须暂停其余全部的工做线程( “Stop The World” ),直到它收集结束。
2.ParNew 收集器
Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其他行为(控制参数、收集算法、回收策略等等)和Serial收集器彻底同样,也是采用复制算法,一样会产生STW。
3.parallel Scavenge 收集器
Parallel Scavenge 收集器相似于ParNew 收集器。Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。因为与吞吐量关系密切,Parallel Scavenge收集器也常常称为“吞吐量优先”收集器。
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。提供了两个参数来控制吞吐量。
-XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间,大于0的毫秒数
-XX:GCTimeRatio 设置吞吐量大小,大于0且小于100的整数,即垃圾收集时间占总时间的比率
4.Serial Old 收集器
Serial收集器的老年代版本,它一样是一个单线程收集器,采用标记-整理算法。它主要有两大用途:一种用途是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种用途是做为CMS收集器的后备方案。
5.Parallel Old 收集器
Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,均可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。
6.CMS 收集器
CMS(Concurrent Mark Sweep)垃圾收集器的关注点更多的是用户线程的停顿时间(提升用户体验),是一种以获取最短回收停顿时间为目标的收集器。它而很是符合在注重用户体验的应用上使用。CMS(Concurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工做。从名字中的Mark Sweep这两个词能够看出,CMS收集器是一种 “标记-清除”算法实现的,它的运做过程相比于前面几种垃圾收集器来讲更加复杂一些。
CMS主要优势:并发收集、低停顿。可是它有下面三个明显的缺点:对CPU资源敏感;没法处理浮动垃圾;它使用的回收算法-“标记-清除”算法会致使收集结束时会有大量空间碎片产生。
7.G1 收集器
G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器, 以极高几率知足GC停顿时间要求的同时,还具有高吞吐量性能特征。
G1将Java堆内存划分为不少个大小相等的独立区域Region,对新生代老年代也没有物理上的划分了,都是多个Region的集合。所以G1是做用在新生代和老年代的。从总体上看是使用的标记-整理算法,也避免了空间碎片的问题,从Region局部来看也有复制算法。
G1收集器在后台维护了一个优先列表,每次根据容许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。这种使用Region划份内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内能够尽量高的收集效率(把内存化整为零)。
8.ZGC 收集器 (目前只支持Linux)
Java 11包含一个全新的垃圾收集器--ZGC,设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 未来还能够扩展实现机制,以支持很多使人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。
ZGC给Hotspot Garbage Collectors增长了两种新技术:着色指针和读屏障。
着色指针(colored pointers):一种将信息存储在指针(或使用Java术语引用)中的技术。由于在64位平台上(ZGC仅支持64位平台),指针能够处理更多的内存,可以使用一些位来存储状态。而取消着色时,采用了多重映射的技巧(没明白);
读屏障:每当应用程序线程从堆加载引用时运行的代码片断(即访问对象上的非原生字段non-primitive field),读屏障的工做是经过测试加载的引用来执行此任务,以查看是否设置了某些位。
参考文档:https://blog.csdn.net/j3t9z7h/article/details/87128403
https://www.cnblogs.com/huanchupkblog/p/10947919.html
https://www.wangxinshuo.cn/2018/09/09/G1/ZGC/
9.Shenendoah 收集器
JDK12中的新版本垃圾收集器--Shenandoah收集器,一款concurrent及parallel的垃圾收集器;跟ZGC同样也是面向low-pause-time的垃圾收集器,不过ZGC是基于colored pointers来实现,而Shenandoah GC是基于brooks pointers来实现。
参看文档:https://blog.csdn.net/qq_33330687/article/details/90314347
停顿时间:垃圾收集器作垃圾回收中断应用执行的时间。-XX:MaxGCPauseMillis
吞吐量:垃圾收集的时间和总时间的占比:1/(1+n),吞吐量为1-1/(1+n)。-XX:GCTimeRatio=n
(愿你的每一行代码,都有让世界进步的力量 ------ fn)