天天看点

JVM之GC垃圾回收全面解析(二)

上节介绍了GC相关概念,算法,和各版本HEAP结构,对GC有了认知。该节开始介绍GC收集器和具体优化。

GC收集器种类 (重点介绍了cms收集器和G1收集器)

 1. Serial:串行收集器,稳定高效停顿时长。新老都串行回收 -XX:+UseSerialGC

 2. ParNew: 并行收集器,新并行,老串行XX:+UseParNewGC

 3. Parallel: 并行收集器+串行老年代 XX:+UseParallelGC

 -XX:+UseAdaptiveSizePolicy 打开自适应GC策略, 在这种模式下, 新生代的大小, eden,survivior的比例, 晋升老年代的 对象年龄等参数会被自动调整,以达到堆大小,吞吐量, 停顿时间之间的平衡点

 4. Parallel:  并行收集器+并行老年代(throughput)  XX:+UseParallelOldGC

 -XX:MaxGCPauseMills,代表最大的GC线程占用的停顿时间,单位是毫秒   -XX:GCTimeRatio,吞吐量-垃圾收集时间占 总时间的比,默认99,允许1%时间做GC.

 如果GC很频繁     GC的最大停顿时间变短,但吞吐量变小,

 如果GC次数很少   最大的停顿时间就会变长,但吞吐量增大

 5. CMS:并发收集器(Concurrent Mark Sweep) 针对老年代

 老年代使用CMS回收器, 新生代使用ParNew回收器;基于标记清除算法(不压缩且

 产生内存碎片,弊端:碎片和浮动垃圾导致另一次FULLGC)  XX:+UseConcMarkSweepGC

( CMS--> Card Marking JVM参数是-XX:UseCondCardMark 在高并发的情况下,Card标记为脏的操作本身就存在  着竞争,使用这个参数可以避免卡片被重复标记为脏,从而提高性能)

 流程:

   (1) .初始标记:标记对象stw很短

   (2) .并发标记:app运行时运行,耗时长

   (3) .重新标记:多线程修复标记,stw比初始标记时间稍长

   (4) .并发清除:回收

总结,互联网项目偏爱,配合ParNew效果很好,少卡顿

 6.G1 :将整个堆划分为多个大小相等的独立区域(Region) -XX:+UseG1GC

      新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合  

    -XX:MaxGCPauseMillis:为G1设置暂停时间目标,默认值为200毫秒;

      -XX:InitiatingHeapOccupancyPercent:当整个Java堆的占用率达到参数值时,

  开始并发标记阶段;默认为45;

   -XX:G1HeapRegionSize:设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region;

 特点:

  (1) .并行并发,充分利用CPU多核,和用户程序同时进行,降低停顿时间

  (2) .分代收集:全部管理,将整个堆划分为多个大小相等的独立区域(Region)

  (3) .多算法结合:无碎片

  (4) .可预测停顿,低停顿同时实现高吞吐,可指定垃圾收集时间不超过N毫秒

 场景:

  (1) .超过50%的堆被活动数据占据

  (2) .对象分配频率或年代升级频率变化大

  (3) .停顿时间长于1S

 原理:

  (1) .将堆化为多个Region,

  (2) .跟踪每个Region收集价值大小,维护一个优先列表

  (3) .根据允许的收集时间,优先回收价值最大的Region

  (4) .避免在堆的全区域垃圾收集

 流程:

  (1) .初始标记:标记对象stw很短

  (2) .并发标记:app运行时运行,耗时长

  (3) .最终标记:多线程修复标记,stw比初始标记时间稍长

  (4) .筛选回收:列表排序,复制压缩整理,并发处理,降时间增吞吐

 总结,新型的收集器,目标是取代cms,核心是划分区域分而治之。应用不广

下面介绍一些GC相关参数

 -Xmx3550m:最大堆设为3550m

 -Xms2000m:初始堆设为2000m

 -Xmn2G:设置新生代2G

 -Xss128K:线程堆栈大小256K,可有更多线程,3-5K个

 -XX:MaxTenuringThreshold:新生代最大年龄,默认15

 -XX:NewRatio=4:新生代与老年代比值1/4,官方建议3/5,占整堆3/8

 -XX:SurvivorRatio=4:幸存区和伊甸区比例,俩个幸存区和伊甸区比例2/4,整新区2/6

 -XX:ParallelGCThreads=20:配置并行收集器的线程数

 -XX: MaxGCPauseMillis=100: 每次年轻代回收的最长时间,自调年轻代大小,以满足此值

 -XX:+UseAdaptiveSizePolicy:并行收集器会自动选择年轻代区大小和Survivor区比例

开始进入选择优化阶段

吞吐量优先  简单的说就是处理快,允许卡顿,比如后台循环计算大量数据,客户允许页面异步接收数据慢一点

 Xmx3550m -Xms3550m -Xmn2g -Xss128k

 -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC

 配置新老垃圾收集方式为都并行收集

响应时间优先 简单的说就是stw卡顿轻微不可查,互联网项目为了客户体验是需要尽快返回无明显卡顿

 -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

 配置新生代为并行收集,老年代为并发收集

 -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5    -XX:+UseCMSCompactAtFullCollection

 -XX:CMSFullGCsBeforeCompaction:并发收集器不对内存空间进行压缩整理,产生“碎片”, 使得运行效率降低。此值设  置运行多少次GC以后对内存空间进行压缩整理。

 -XX:+UseCMSCompactAtFullCollection:压缩老年代。可能影响性能,但是可以消除碎片

来自美团的GC经验心得

 各分区的大小对GC的性能影响很大。如何将各分区调整到合适的大小,分析活跃数据的大小是很好的切入点。

 活跃数据的大小是指,应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用 空间的大小。可以通过GC日志中Full GC之后老年代数据大小得出,比较准确的方法是在程序稳定后,多次获取GC数据, 通过取平均值的方式计算活跃数据的大小。活跃数据和各分区之间的比例关系如下:

 根据GC日志获得老年代的活跃数据大小为300MB,那么各分区大小可以设为:

  总堆:1200MB = 300MB × 4

  新生代:450MB = 300MB × 1.5

  老年代: 750MB = 1200MB - 450MB*

这部分设置仅仅是堆大小的初始值,后面的优化中,可能会调整这些值,具体情况取决于应用程序的特性和需求。

总结:对大部分普通web项目,都不需要调整GC,只需要减少大对象的持续引用,和循环创建class等,不会有太大问题。电商和互联网项目要求就比较高了,

最后引用下imprtnow的测试各收集器处理GC时间的时间比较=------并行还是极快的

JVM之GC垃圾回收全面解析(二)