读书笔记--《分布式Java应用:基础与实践》 第三章:深入了解JVM(垃圾清理)

配合《深入了解Java虚拟机》一起总结下

慨念

·并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。·
并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。

一.垃圾清理算法总结

  1. 标记的方法
    (1)引用计数式垃圾收集,弄一个计数器,对象每多一次引用则加1,为0则为可清楚。清理时需要遍历每一个对象被引用的次数,堆对象数量太多的话就花销太大了,因此没有主流垃圾收集器使用此算法。
    (2)追踪式垃圾收集,先寻找出必须存活的对象作为GC Roots,再用GC Roots继续往下追踪引用的对象。寻找GC Roots的操作不可以并发,往下追踪的时候可以并发。

2.清理的方法

(1)标记-清除算法
首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。缺点:执行效率不稳定且容易产生不连续的内存碎片。
(2) 标记-复制算法
将需要标记保留的对象全部复制到另一块内存上去,再将原来的内存清理掉。缺点:这样做需要有内存进行分配担保,比如常见的新生代在往幸存区复制时,如果幸存区内存不够容纳,则需要老年代进行分配担保。
(3)标记-整理算法
标记后,将幸存的对象统一往一个方向移动后再清理边界后的对象。缺点:移动时会改变对象地址,所以需要暂停程序。

3.常用的垃圾收集器组合
这里就不分开讲每个垃圾收集器了

  1. ParNew(年轻代) + CMS(老年代)
    ParNew 采用标记-复制算法,可以多线程并行收集垃圾,由eden+s0 复制到s1,并清理eden+s0 ,下次清理则由eden+s1 复制到s0,并清理eden+s1。清理过程不可以并发执行。
    CMS 采用标记-清除算法,老年代的对象比较多,直接标记花费时间太长,因此需要考虑并发标记,CMS的标记过程比较复杂,分为4个步骤
    1)初始标记(CMS initial mark)
    找出GC Roots对象,不可并发
    2)并发标记(CMS concurrent mark)
    从GC Roots继续往下追踪对象,可以并发
    3)重新标记(CMS remark)
    将上一步并发中程序新增的引用重新标记,避免新增的引用应该被标记的却没有标记的情况发生
    4)并发清除(CMS concurrent sweep)
    将未被标记的对象清理,由于会产生大量的不连续内存碎片,
    缺点:
    1.并发标记占用线程资源,减少吞吐量
    2.无法清理并发标记过程中程序产生的新对象,即浮动垃圾,只能下一次才能进行清理,这就要求CMS不能够在老年代内存即将用完才进行垃圾收集
    3.产生大量不连续的内存碎片,最终会导致大对象无法找到连续内存不得不提前进行FULL GC

调优:
1.调整-XX:CMSInitiatingOccupancyFraction参数,默认为68%。调整CMS老年代清理阈值,过低会导致频繁清理,过高则可能会导致并发失败,会冻结用户线程的执行,会临时启用Serial Old收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了。
2.调整-XX:+UseCMS-CompactAtFullCollection开关参数(默认是开启的,此参数从JDK 9开始废弃),在Full GC的时候进行碎片整理
调整-XX:CMSFullGCsBefore-Compaction(此参数从JDK 9开始废弃)默认为0,先进行参数定义次数的不进行碎片整理的Full GC后,下一次进入Full GC前会先进行碎片整理。

3.-XX:SurvivorRation:调整eden与幸存区的比例,默认为8,eden太小则会频繁清理,太大可能能导致行存区太小,因而大对象很快进入老年区
4.-XX:ParallelGCThreads:调整垃圾收集器的线程数
5.-XX:PretenureSizeThreshold:晋升老年代对象大小,此参数太小的话会导致老年代频繁清理,太大则会导致新生代频繁清理

  1. Parallel Scavenge + Parallel Old
    Parallel Scavenge和ParNew一样也是基于标记-复制算法,不同的是Parallel Scavenge更加注重于吞吐量。不可以并发
    Parallel Old,支持多线程并发收集,基于标记-整理算法实现。
    缺点:
    1)注重吞吐量,单次停顿时间会比较长
    2)标记-整理的整理期间将对象进行移动不能够并发操作。

调优:
**-XX:MaxGCPauseMillis:收集器将尽力保证内存回收花费的时间不超过用户设定值。停顿时间过少,总的花费时间就比较多,吞吐量就下来了。
-XX:GCTimeRatio:垃圾收集时间占总时间的比率。
以上两个参数并非设定虚拟机就一定能够达到,虚拟机只能尽量向这两个参数目标运行
-XX:+UseAdaptiveSizePolicy :此参数激活后就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
**
3.Garbage First收集器
也就是G1收集器

G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。

调优:
-XX:MaxGCPauseMillis参数指定的停顿时间,太低话会导致每次清理都无法清理足够量的垃圾,回收足够的内存,如果内存回收的速度赶不上内存分配的速度,G1收集器也要被迫冻结用户线程执行,导致Full GC而产生长时间“Stop The World”

什么时候Stop The World?
1.标记算法搜索GC Roots对象,以及并发标记后的重新标记时会Stop The World。很短
2.采用标记-整理算法时,在移动对象在内存的位置时会Stop The World。较短
3.Full GC而产生长时间Stop The World。很长

你可能感兴趣的:(读书笔记--《分布式Java应用:基础与实践》 第三章:深入了解JVM(垃圾清理))