JVM调优(二)内存收集器原理和参数调整

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bbbbln/article/details/82804723

调优

根据上节的原理分析,进行调优可以从硬件、操作系统、JVM以及程序四个方面入手。

用更好的硬件如更大的内存、更快的CPU等,操作系统的话,需要自己去摸索,window不行就换linux,linux不行就换unix,甚至其它商业机。对于这两点,如果程序确实解决不了,可以把问题抛给运维。但作为一名开发者,我们现在可以从JVM和程序两方面尝试优化。

JVM方面,从内存分配管理、GC策略入手。

先搞清楚垃圾收集的几个重要概念:

并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC。Major GC的速度一般会比Minor GC慢10倍以上。

吞吐量:就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

简单分析各GC收集器:

Serial收集器是最基本的收集器。简单高效。单线程跑,需要stop the world。默认用于JVM client模式的新生代。使用标记-整理算法。

ParNew收集器其实就是Serial收集器的多线程版本,也需要stop the world。是JVM server模式的新生代的优先选择。(很重要的原因:除了Serial收集器外,目前只有它能与CMS收集器配合工作)。使用标记-整理算法。

CMS收集器收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。但CMS作为老年代的收集器,无法与新生代收集器Parallel Scavenge配合工作。基于“标记—清除”算法。需要经历:初始标记(CMS initial mark,非常快,stop the world)、并发标记(CMS concurrent mark,慢)、重新标记(CMS remark,快,stop the world)、并发清除(CMS concurrent sweep,慢),四个阶段。CMS收集器对CPU资源非常敏感,产生内存碎片,无法清除浮动垃圾。

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,高吞吐量。“标记-整理”算法。

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

G1是面向服务端的,将来替换CMS。基于并行和并发、低延迟以及暂停时间更加可控的区域化分代式垃圾收集器。 G1 重塑了整个Java堆区,不再区分新生代和老年代,而是将Java堆区分成约2048 个大小相同的独立Region 块,每个Region 块可能连续,也可能不连续,大小被控制在1MB~32MB之间。这是因为G1收集器在执行内存回收时,能够优先释放掉整个Java堆区中一些占用内存较大的Region块,而无需像其他收集器一样直接扫描整个java堆区,因此能更好地提升GC的回收效率和缩短”Stop-the-World” 机制的暂停时间以换取更大程序吞吐量

G1回收步步骤:1、所有Eden区清空,Survival和Older区增大。2、并发标记,标出垃圾最多的一些O区,过程:扫描root,并发标记,二次标记阶段和清理阶段。3、混合GC,就是清理阶段。可以查看G1收集器原理,说的非常详细。

收集算法:

标记-复制(标记复制清理)、标记-清理(标记清理)、标记-整理(标记清理移动)。

介绍完这些内容,下面开始讲优化方案。

代大小调优

关注参数:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold

-Xms(最小堆内存)和-Xmx(最大堆内存)设置相同值,避免堆内存不断申请扩大。

-Xmn(新生代内存):应与堆内存里的老年代内存比率为1:2。而新生代里又有Eden和Survivor(S0,S1,比率1:1)。Eden与S0(或S1)的比例关系用-XX:SurvivorRation来控制。

-XX:MaxTenuringThreshold:控制新生代里对象经历多少次MinorGC后才转入旧生代(不是转入Survivor区,每次MinorGC后存活对象的生存点是Survivor区)。

避免将新生代内存设置太小,否则:1、容易频繁引起MinorGC,2、更多的新生代对象直接进入老年代,更易引起FullGC。

调大JVM Heap内存,并增大新生代内存。尽量让对象在MinorGC阶段被回收。但Jvm Heap内存增大后,单次GC的时间就会增加。新生代相对调大后(MinorGC时间变长),旧生代变小,易引起FullGC。所以在调优时可以多试几次以取得更好效果。

新生代的Survivor区域要合适。太大造成Eden空间小,容易MinorGC;太小MinorGC后对象直接进入老年代易引起FullGC。

内存比较合理的情况下,可以试着调大新生代的存活周期。

GC策略调优

串行GC性能较差,实际环境以并行和并发GC为主。

算法上“以时间换空间,以空间换时间”,这句话在这里其实也是适用。

对于延迟比较严格的操作,可以采用CMS收集器(标记清除,快),能够减少GC动作对应用的暂停时间。

优化时,本着减少FullGC为首要原则。然后,MinorGC频繁执行会造成cpu的us占用过高。

总得来说,JVM的调优是一个经验问题,要反复去调节,才能达有满意的效果。当然除此之外,还受限于公司成本、公司制度等原因。_

猜你喜欢

转载自blog.csdn.net/bbbbln/article/details/82804723