写在之前的话

最近在工作中总是遇到服务的QPS在压测的时间比较低的情况。于是就开始了性能优化之旅,这个过程是很是曲折。一开始的时候认为是服务的业务逻辑比较多,大部分的时间都花在优化业务逻辑,减少与其他服务和数据库等接口的调用,使用缓存等方式提高性能。上述的工作都是对性能有提升的,但是对于一个熟练的农民工而言,对语言的特性已经成竹在胸,很难通过优化一两句代码获得质的提升。由于目前都是采用微服务的开发方式,JVM的参数是自己设置的,就想着在这个方面去优化一下。

通过在压测过程中检测(使用jstat工具)gc的次数及时间来分析,这个服务在压力测试中的gc次数已经到了影响提升性能的关键因素。于是便着手减少gc的次数。

本次优化的目的有两个

1、将转移到老年代的对象数量降低到最小;

2、减少full GC的执行时间;

从网上看了一些JVM调优的方法,其中一位仁兄分享的程序调优方法觉得跟自己的情况特别服务,特别的在这里跟大家分享下:

一切都是为了这一步,调优,在调优之前,我们需要记住下面的原则:

1、多数的Java应用不需要在服务器上进行GC优化;

2、多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;

3、在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);

4、减少创建对象的数量;

5、减少使用全局变量和大对象;

6GC优化是到最后不得已才采用的手段;

7、在实际使用中,分析GC情况优化代码比优化GC参数要多得多;

为了达到上面的目的,一般地,你需要做的事情有:

1、减少使用全局变量和大对象;

2、调整新生代的大小到最合适;

3、设置老年代的大小为最合适;

4、选择合适的GC收集器;

JVM堆栈组成及划分

有了上面的思路,下面我们一起来了解下JVM堆栈的结构。JVM堆栈由三部分组成:新生代、老年代和永久代。下图形象的描述了各个区的划分:

clip_image002

从图中我们可以看出堆内存空间的结构

1. JVM堆内存由三大区组成:新生代(Yong Generation)、老年代(Tenured Generation)和永久带(Perm Generation);

2. 新生代由三部分组成:一个Eden区、两个Survivor区;

3. 两个Survivor区一个叫From,一个叫To,从字面叫法上可以看出两个的作用;

4. Eden和Survivor默认比例是8:1。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

GC过程中对象的移动过程

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

clip_image004

JVM运行时关键参数设置

参数名称

说明

Tip

-Xms

堆的最小值

32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制

-Xmx

堆空间的最大值

32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制

-Xmn

年轻代大小

整个堆大小=年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-Xss

设置每个线程的堆栈大小

JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内 存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

-Xms

堆的最小值

32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制

有关年轻代的JVM参数

1) -XX:NewSize和-XX:MaxNewSize

用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。

2) -XX:SurvivorRatio

用于设置Eden和其中一个Survivor的比值,这个值也比较重要。设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

3) -XX:NewRatio=4

设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

4) -XX:+PrintTenuringDistribution

这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。

5) -XX:MaxPermSize=16m

建议设置持久代大小为16m。

6) -XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold

用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。

-XX:MaxTenuringThreshold=0。设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

吞吐量收集器

1) -XX:+UseSerialGC

激活串行垃圾收集器 例如单线程面向吞吐量的垃圾收集器 推荐用于只有单个CPU的JVM

2) -XX:+UseParallelGC

使用多线程并行执行年轻代垃圾收集

3) -XX:+UseParallelOldG

除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集

4) -XX:ParallelGCThreads

-XX:ParallelGCThreads=<value> 指定并行垃圾收集的线程数量

5) -XX:-UseAdaptiveSizePolicy

垃圾收集器能将堆大小动态变动像GC设置一样应用到不同的堆区域,只要有证据表明这些变动将能提高GC性能

6) -XX:GCTimeRatio

-XX:GCTimeRatio=<value>指定JVM吞吐量要达到的目标值
指定目标应用程序线程的执行时间和总的程序执行时间达到N/(N+1)的目标比值
通过-XX:GCTimeRatio=9我们要求应用程序线程在整个执行时间中至少9/10是活动的(因此,GC线程占用其余1/10)
-XX:GCTimeRatio的默认值是99,也就是说,应用程序线程应该运行至少99%的总执行时间

7) -XX:MaxGCPauseMillis

通过-XX:GCTimeRatio=<value>告诉JVM最大暂停时间目标值[ms为单位]

CMS收集器

1) -XX:+UseConcMarkSweepGC

激活CMS收集器 JVM默认使用的是并行处理器

2) -XX:UseParNewGC

使用CMS收集器时 激活年轻代使用多线程并行执行垃圾回收
注意:
最新的JVM版本,当使用-XX:+UseConcMarkSweepGC时,-XX:UseParNewGC会自动开启。
因此,如果年轻代的并行GC不想开启,可以通过设置-XX:-UseParNewGC来关掉。

3) -XX:+CMSConcurrentMTEnabled

并发的CMS阶段以多线程执行 默认开启

4) -XX:ConcGCThreads

-XX:ConcGCThreads=<value>定义并发CMS中运行的线程数
如果还标志未设置,JVM会根据并行收集器中的-XX:ParallelGCThreads参数的值来计算出默认的并行CMS线程数。该公式是ConcGCThreads = (ParallelGCThreads + 3)/4。因此,对于CMS收集器
-XX:ParallelGCThreads标志不仅影响“stop-the-world”垃圾收集阶段,还影响并发阶段。

5) -XX:CMSInitiatingOccupancyFraction

-XX:CMSInitiatingOccupancyFraction=来设置,该值代表老年代堆空间的使用率。比如,value=75意味着第一次CMS垃圾收集会在老年代被占用75%时被触发。通常CMSInitiatingOccupancyFraction的默认值为68(之前很长时间的经历来决定的)。

6) -XX:+UseCMSInitiatingOccupancyOnly

命令JVM不基于运行时收集的数据来启动CMS垃圾收集周期
JVM通过CMSInitiatingOccupancyFraction的值进行每一次CMS收集,而不仅仅是第一次

7) -XX:+CMSClassUnloadingEnabled

CMS默认不会对永久代进行垃圾回收 如果希望对永久代进行垃圾回收 可以设置此标志
注意,即使没有设置这个标志,一旦永久代耗尽空间也会尝试进行垃圾回收,但是收集不会是并行的,而再一次进行Full GC。

8) -XX:+CMSIncrementalMode

开启CMS收集器的增量模式
ps:增量模式会经常暂停CMS过程 以便对应用程序作出完全的让步

9) -XX:+ExplicitGCInvokesConcurrent和-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

1.-XX:+ExplicitGCInvokesConcurrent
命令JVM无论什么时候调用系统GC,都执行CMS GC,而不是Full GC
2.-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
保证当有系统GC调用时,永久代也被包括进CMS垃圾回收的范围内。因此,通过使用这些标志,我们可以防止出现意料之外的”stop-the-world”的系统GC。

10) -XX:+DisableExplicitGC

该标志将告诉JVM完全忽略系统的GC调用(不管使用的收集器是什么类型)

总结

JVM参数是死的,你的服务所对应的场景才是活的。在调优的过程中需要根据自己的业务需要不断的调整参数、测试、再调整的迭代方式直到性能达到最优。

推荐大家使用jstat这款Java自动的检测GC的工具,对于查看系统的GC信息有很大的帮助。

参考资料

官方提供的JVM参数一览表:

http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

https://blog.csdn.net/rodesad/article/details/51544977

版权声明:本文为rushoooooo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/rushoooooo/p/9642066.html