前一节,咱们讲解了内存回收的方法论,从基本的回收对象是否存活引入了:直接引用计数法(对象添加引用计数器)、可达性分析法(引用对象从GC Root出发。经过引用链查找),正由于直接引用计数法没法解决循环引用问题,引入可达性分析法。而后引入垃圾对象回收的算法方法论:最简单的是“标记-清除法”:使用可达性分析法将内存对象标记为垃圾对象,而后清除垃圾对象,标记-清除法在对象存活时间比较长的内存区域效率低下:会消耗大量时间标记清除大量内存对象,标记清除法清除垃圾对象的时候会致使不连续的内存空间,引出空间碎片化问题。因此为了解决上面问题引出了标记-复制算法:将空间分为两部分:正在使用的其中一块空间,当对象标记为垃圾对象,须要出发GC时候,将存对象一次性移到另外一块内存,而且使用移动内存指针方式让另外一块内存空间连续话,而后gc掉第一块全部内存,解决了内存碎片化问题,在内存对象存活周期短的区域,只须要移动少许的存活对象到另外一块区域,效率高,因此适合在堆内存里面的新生代;对于老年代;咱们须要引出:标记-整理算法:将垃圾对象进行标记,而后gc时候经过指针将内存对象连续化。这一讲咱们来研究具体的内存回收实现:垃圾回收器。 算法
咱们主要分析经常使用的虚拟机(HotSpot)的垃圾回收器。
展现了七种做用于不一样分代的收集器,若是两个收集器之间存在连线,就说明它们能够搭配使用,图中收集器所处的区域,则表示它是属于新生代收集器抑或是老年代收集器。浏览器
咱们从简单到复杂主要分析5种垃圾回收器:Serial收集器、ParNew收集器、Parallel Scavenge收集器、CMS收集器、G1收集器.多线程
原理/是什么:单线程垃圾回收器,进行垃圾回收时候,用户线程所有中止,直到垃圾回收结束。 并发
图解:
单线程工做的收集器,但它的“单线程”的意义并不只仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工做,更重要的是强调在它进行垃圾收集时,必须暂停其余全部工做线程,直到它收集结束。“Stop The World”这个词语也 许听起来很酷,但这项工做是由虚拟机在后台自动发起和自动完成的,在用户不可知、不可控的状况 下把用户的正常工做的线程所有停掉,这对不少应用来讲都是不能接受的。
特色:
缺点:“Stop The World”:它进⾏垃圾收集时,必须暂停其余全部的⼯做线程,直到它收集结束。在⽤户不可⻅的状况下把⽤户正常⼯做的线程所有停掉。(从Serial收集器到Parallel收集器,再到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器,最终至如今垃圾收集器的最前沿成果Shenandoah和ZGC 等,咱们看到了一个个愈来愈构思精巧,愈来愈优秀,也愈来愈复杂的垃圾收集器不断涌现,用户线 程的停顿时间在持续缩短,可是仍然没有办法完全消除)
优势:多⽤于桌⾯应⽤,是客户端Client模式下的虚拟机。桌⾯应⽤暂用内存⼩,进⾏垃圾回收的时间⽐较短,只要不频繁发⽣停顿就能够接受。(由于是单线程的,因此消耗cpu跟内存相对比较小)布局
回顾:
上一节咱们讲解了垃圾回收器里面第一种垃圾回收器Serial。Serial垃圾回收器采用单线程垃圾回收,他的性能是比较差的。为了解决Serial的性能问题,咱们引入了另外一种垃圾回收器:ParNew垃圾回收器。
原理:ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之 外,其他的行为包括Serial收集器可用的全部控制参数(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规 则、回收策略等都与Serial收集器彻底一致性能
图解:
一、ParNew收集器除了支持多线程并行收集以外,其余与Serial收集器相比并无太多创新之处,但它 倒是很多运行在服务端模式下的HotSpot虚拟机,尤为是JDK 7以前的遗留系统中首选的新生代收集 器,其中有一个与功能、性能无关但其实很重要的缘由是:除了Serial收集器外,目前只有它能与CMS 收集器配合工做。
二、CMS做为老年代的收集器,却没法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工做[1],因此在JDK 5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者 Serial收集器中的一个。ParNew收集器是激活CMS后(使用-XX:+UseConcMarkSweepGC选项)的默 认新生代收集器,也可使用-XX:+/-UseParNewGC选项来强制指定或者禁用它。
三、G1是一个面向全堆的收集器,不 再须要其余新生代收集器的配合工做。因此自JDK 9开始,ParNew加CMS收集器的组合就再也不是官方 推荐的服务端模式下的收集器解决方案了。官方但愿它能彻底被G1所取代,甚至还取消了ParNew加 Serial Old以及Serial加CMS这两组收集器组合的支持(其实本来也不多人这样使用) 网站
特色:
---一、ParNew 收集器除了多线程收集以外,其余与 Serial 收集器相⽐并无太多创新之处,但它倒是许多运⾏在 Server 模式下(以前咱们的Serial是运行在client端的)的虚拟机中⾸选的新⽣代收集器,其中有⼀个与性能⽆关但很重要的缘由是,除了 Serial 收集器外,⽬前只有它能与CMS收集器配合⼯做(ParNew通常跟CMS一块儿来使用;咱们的ParNew通常用来回收新生代,而CMS通常用来回收老年代)。
---二、使⽤-XX: ParallelGCThreads 参数来限制垃圾收集的线程数。(这个设置有一个参照点:你们知道咱们的cpu有一个核数,这个核数表明了咱们cpu同时能处理多少个线程的数量,若是cpu核数是8的话,建议将咱们的ParallelGCThreads参数值设置为8)
---三、多线程操做存在上下⽂切换的问题,因此建议将-XX: ParallelGCThreads设置成和CPU核数相同,若是设置太多的话就会产⽣上下⽂切换消耗。(因此并非咱们这个ParallelGCThreads参数越大越好、他会有上下文切换的失效)spa
收集器的上下文语境中:并发与并行
并发:描述GC线程跟用户线程之间关系:GC线程跟用户线程同时运行。
并行:描述GC线程间关系:多个GC线程同时运行,用户线程暂停。线程
回顾:上一节咱们讲解了ParNew垃圾回收器,ParNew垃圾回收器是在Serial基础上实现的一个多线程的扩充。多线程的垃圾回收器除了ParNew以外,还有Parallel Scavenge垃圾回收器。 3d
是什么:控制的吞吐量的ParNew收集器(也能够叫作:基于标记-复制算法实现的多线程吞吐量优先的垃圾回收器)
特色: 一、多线程垃圾回收 二、关注吞吐量 三、参数可调。
与其余垃圾回收器区别: 关注点:其余垃圾回收线程关注缩短垃圾收集时用户线程的停顿时间。Parallel Scavenge收集器的目标则是达到一个可控制的吞吐 量(Throughput)
吞吐量: 吞吐量=用户线程执行时间/用户线程执行时间+GC线程执行时间
回顾: 上一节咱们讲解了两个概念,一个是并发一个是并行,而且咱们讲解了ParNew跟Parallel Scavenge收集器,他们都是并行的垃圾回收器,当工做线程运行到一半时候会被阻断运行GC线程,GC垃圾回收以后会再次运行工做线程。除了并行以外,还有一种垃圾回收器他是能够并发执行的。CMS垃圾收集器。
是什么?: CMS(Concurrent Mark Sweep)基于标记-清除算法实现的一种以获取最短回收停顿时间为目标的收集器。目前很 大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用一般都会较为 关注服务的响应速度,但愿系统停顿时间尽量短,以给用户带来良好的交互体验。
图解4步骤:
出CMS收集器是基于标记-清除算法实现的整个过程分为四个步骤,包括: 1)初始标记(CMS initial mark):是标记GC Roots能直接关联到的对象,速度很快.
2)并发标记(CMS concurrent mark):从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长可是不须要停顿用户线程,能够与垃圾收集线程一块儿并发运行;
3)从新标记(CMS remark):为了修正并发标记期间,因用户程序继续运做而致使标记产生变更的那一部分对象的标记记录;这个阶段的停顿时间一般会比初始标记阶段稍长些,但也远比并发标记阶段的时间短;
4)并发清除(CMS concurrent sweep):清理删除掉标记阶段判断的已经死亡的对象,因为不须要移动存活对象,因此这个阶段也是能够与用户线程同时并发的。
初始标记、从新标记这两个步骤仍然须要“Stop The World”.
因为在整个过程当中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程均可以与用户线程一 起工做,因此从整体上来讲,CMS收集器的内存回收过程是与用户线程一块儿并发执行的。能够比较清楚地看到CMS收集器的运做步骤中并发和须要停顿的阶段。
特色:
优势:并发收集、低停顿。
缺点:
一、对CPU资源⾮常敏感(特别是单核机器-并发会占用更多资源)
二、⽆法处理浮动垃圾,并发清除时候已经产生了一些垃圾;浮动垃圾:程序在进⾏并发清除阶段⽤户线程所产⽣的新垃圾。
三、标记-清除暂时空间碎⽚。
回顾:上一节咱们讲解CMS垃圾收集器,这一节咱们讲解更加高效的垃圾收集器:G1收集器。
是什么?: G1是⼀款⾯向服务端应⽤的垃圾收集器。是基于标记-整理法;首先他对性能的要求会特别高。
流程步骤:
·初始标记(Initial Marking): 仅仅只是标记一下GC Roots能直接关联到的对象,而且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段须要 停顿线程,但耗时很短,并且是借用进行Minor GC的时候同步完成的,因此G1收集器在这个阶段实际 并无额外的停顿。
·并发标记(Concurrent Marking): 从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要从新处理SATB记录下的在并发时有引用变更的对象。
·最终标记(Final Marking): 对用户线程作另外一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少许的SATB记录。
·筛选回收(Live Data Counting and Evacuation): 负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所指望的停顿时间来制定回收计划,能够自由选择任意多个Region 构成回收集,而后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的所有空间。这里的操做涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。从上述阶段的描述能够看出,G1收集器除了并发标记外,其他阶段也是要彻底暂停用户线程的, 换言之,它并不是纯粹地追求低延迟,官方给它设定的目标是在延迟可控的状况下得到尽量高的吞吐 量,因此才能担当起“全功能收集器”的重任与指望
特色: 跟CMS相比他有什么特色呢?
G1会将内存块分红多个Region.Region就是一个区域。(咱们以前在将CMS、Serial垃圾收集器的时候,咱们都采用了新生代、老年代分区的收集方法,而在G1的时候,对新生代、老年代就不是特别敏感了、他将咱们的每一块内存分红了Region,内存区域快会分红多个Region。咱们垃圾回收时候维护的是Region里面的信息。)区分Region有什么用呢?Region里面有与之对应的RememberSet。当进⾏内存回收时,在 GC 根节点的枚举范围中加⼊ Remembered Set 便可保证不对全堆扫描也不会有遗漏 检查Reference引⽤的对象是否处于不一样的Region。
优点:
一、空间整合: Region内基于“标记⼀整理”算法实现为主(避免空间垃圾碎片)和Region之间采⽤复制算法(Region之间存活对象比较少,复制算法效率高)实现的垃圾收集。
二、可预测的停顿:(缘由:由于咱们操做的是region,垃圾回收信息都存放在region里面,因此他是能够预测停顿时间的)这是 G1 相对于 CMS 的另⼀⼤优点,下降停顿时间是 G1 和 CMS 共同的关 注点,但 G1 除了追求低停顿外,还能建⽴可预测的停顿时间模型。
三、在 G1 以前的其余收集器进⾏收集的范围都是整个新⽣代或者⽼年代,⽽ G1 再也不是这样。使⽤ G1 收集器时,Java 堆的内存布局就与其余收集器有很⼤差异,它将整个 Java 雄划分为多个⼤⼩相等的独⽴区域(Region),虽然还保留有新⽣代和⽼年代的概念,但新⽣代和⽼年
代再也不是物理隔髙的了,它们都是⼀部分 Region(不须要连续)的集合。
四、G1 收集器之因此能建⽴可预测的停顿时间模型,是由于它能够有计划地避免在整个 Java 堆
中进⾏全区域的垃圾收集。G1 跟踪各个 Regions ⾥⾯的垃圾堆积的价值⼤⼩(回收所得到的空间⼤⼩以及回收所需时间的经验值),在后台维护⼀个优先列表,每次根据容许的收集时间,优先回收价值最⼤的 Region(这也就是 Garbage- Firsti 名称的来由)。这种使⽤Region 划份内存空间以及有优先级的区域回收⽅式,保证了 G1 收集器在有限的时间内能够获取尽量⾼。
补充:如今咱们不少公司仍是在使用CMS垃圾回收器,不多会用JDK1.九、JDK.10.因此咱们还不多用G1。