jvm8:调优实战

-JVM参数

  1. 通用JVM参数

-server

如果不配置该参数,JVM会根据应用服务器硬件配置自动选择不同模式,server模式启动比较慢,但是运行期速度得到了优化,适合于服务器端运行的JVM。

-client

启动比较快,但是运行期响应没有server模式的优化,适合于个人PC的服务开发和测试。

-Xmx

设置java heap的最大值,默认是机器物理内存的1/4。这个值决定了最多可用的Java堆内存:分配过少就会在应用中需要大量内存作缓存或者临时对象时出现OOM(Out Of Memory)的问题;如果分配过大,那么就会因PermSize过小而引起的另外一种Out Of Memory。所以如何配置还是根据运行过程中的分析和计算来确定,如果不能确定还是采用默认的配置。

-Xms

设置Java堆初始化时的大小,默认情况是机器物理内存的1/64。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会降低运行速度,分配多了也浪费。

-XX:PermSize

初始化永久内存区域大小。永久内存区域全称是Permanent Generation space,是指内存的永久保存区域,程序运行期不对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。 如果你的WEB APP下用了大量的第三方jar,其大小超过了jvm默认的PermSize大小(4M)那么就会产生此错误信息了。

-XX:MaxPermSize

设置永久内存区域最大大小。

-Xmn

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

按照Sun的官方设置比例,则上面的例子中年轻代的大小应该为2048*3/8=768M。

-XX:NewRatio

控制默认的Young代的大小,例如,设置-XX:NewRatio=3意味着Young代和老年代的比率是1:3。换句话说,Eden和Survivor空间总和是整个堆大小的1/4。

如图中的实际设置,-XX:NewRatio=2,-Xmx=2048,则年轻代和老年代的分配比例为1:2,即年轻代的大小为682M,而老年代的大小为1365M。查看实际系统的jvm监控结果为:

内存池名称: Tenured Gen

Java 虚拟机最初向操作系统请求的内存量: 3,538,944 字节

Java 虚拟机实际能从操作系统获得的内存量: 1,431,699,456 字节

Java 虚拟机可从操作系统获得的最大内存量: 1,431,699,456 字节。请注意,并不一定能获得该内存量。

Java 虚拟机此时使用的内存量: 1,408,650,472 字节

即:1,408,650,472 字节=1365M,证明了上面的计算是正确的。

-XX:SurvivorRatio

设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6。越大的survivor空间可以允许短期对象尽量在年青代消亡;如果Survivor空间太小,Copying收集将直接将其转移到老年代中,这将加快老年代的空间使用速度,引发频繁的完全垃圾回收。

如下图:

SurvivorRatio的值设为3,Xmn为768M,则每个Survivor空间的大小为768M/5=153.6M。

-XX:NewSize

为了实现更好的性能,您应该对包含短期存活对象的池的大小进行设置,以使该池中的对象的存活时间不会超过一个垃圾回收循环。新生成的池的大小由 NewSize 和 MaxNewSize 参数确定。通过这个选项可以设置Java新对象生产堆内存。在通常情况下这个选项的数值为1024的整数倍并且大于1MB。这个值的取值规则为,一般情况下这个值-XX:NewSize是最大堆内存(maximum heap size)的四分之一。增加这个选项值的大小是为了增大较大数量的短生命周期对象。增加Java新对象生产堆内存相当于增加了处理器的数目。并且可以并行地分配内存,但是请注意内存的垃圾回收却是不可以并行处理的。作用跟-XX:NewRatio相似, -XX:NewRatio是设置比例而-XX:NewSize是设置精确的数值。

-XX:MaxNewSize

通过这个选项可以设置最大Java新对象生产堆内存。通常情况下这个选项的数值为1 024的整数倍并且大于1MB,其功用与上面的设置新对象生产堆内存-XX:NewSize相同。一般要将NewSize和MaxNewSize设成一致。

-XX:MaxTenuringThreshold

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

如下图:

-XX:MaxTenuringThreshold参数被设置成5,表示对象会在Survivor区进行5次复制后如果还没有被回收才会被复制到老年代。

-XX:GCTimeRatio

设置垃圾回收时间占程序运行时间的百分比。该参数设置为n的话,则垃圾回收时间占程序运行时间百分比的公式为1/(1+n) ,如果n=19表示java可以用5%的时间来做垃圾回收,1/(1+19)=1/20=5%。

-XX:TargetsurvivorRatio

该值是一个百分比,控制允许使用的救助空间的比例,默认值是50。该参数设置较大的话可提高对survivor空间的使用率。当较大的堆栈使用较低的SurvivorRatio时,应增加该值到80至90,以更好利用救助空间。

-Xss

设置每个线程的堆栈大小,根据应用的线程所需内存大小进行调整,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。当这个选项被设置的较大(>2MB)时将会在很大程度上降低系统的性能。因此在设置这个值时应该格外小心,调整后要注意观察系统的性能,不断调整以期达到最优。

JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。

-Xnoclassgc

这个选项用来取消系统对特定类的垃圾回收。它可以防止当这个类的所有引用丢失之后,这个类仍被引用时不会再一次被重新装载,因此这个选项将增大系统堆内存的空间。禁用类垃圾回收,性能会高一点;

  1. 串行收集器参数

-XX:+UseSerialGC:

设置串行收集器 。

  1. 并行收集器参数

-XX:+UseParallelGC:

选择垃圾收集器为并行收集器,此配置仅对年轻代有效,即上述配置下,年轻代使用并行收集,而老年代仍旧使用串行收集。采用了多线程并行管理和回收垃圾对象,提高了回收效率,提高了服务器的吞吐量,适合于多处理器的服务器。

-XX:ParallelGCThreads

配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

-XX:+UseParallelOldGC:

采用对于老年代并发收集的策略,可以提高收集效率。JDK6.0支持对老年代并行收集。

-XX:MaxGCPauseMillis

设置每次年轻代并行收集最大暂停时间,如果无法满足此时间,JVM会自动调整年轻代大小以满足此值。

-XX:+UseAdaptiveSizePolicy:

设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

  1. 并发收集器参数

-XX:+UseConcMarkSweepGC

指定在 老年代 使用 concurrent cmark sweep gc。gc thread 和 app thread 并行 ( 在 init-mark 和 remark 时 pause app thread)。app pause 时间较短 , 适合交互性强的系统 , 如 web server。它可以并发执行收集操作,降低应用停止时间,同时它也是并行处理模式,可以有效地利用多处理器的系统的多进程处理。

-XX:+UseParNewGC

指定在 New Generation 使用 parallel collector, 是 UseParallelGC 的 gc 的升级版本 , 有更好的性能或者优点 , 可以和 CMS gc 一起使用

-XX:+UseCMSCompactAtFullCollection:

打开对老年代的压缩。可能会影响性能,但是可以消除碎片,在FULL GC的时候, 压缩内存, CMS是不会移动内存的, 因此, 这个非常容易产生碎片, 导致内存不够用, 因此, 内存的压缩这个时候就会被启用。 增加这个参数是个好习惯。

-XX:+CMSIncrementalMode:

设置为增量模式。适用于单CPU情况

-XX:CMSFullGCsBeforeCompaction

由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生"碎片",使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+CMSClassUnloadingEnabled

使CMS收集持久代的类,而不是fullgc

-XX:+CMSPermGenSweepingEnabled

使CMS收集持久代的类,而不是fullgc。

-XX:-CMSParallelRemarkEnabled

在使用 UseParNewGC 的情况下 , 尽量减少 mark 的时间。

-XX:CMSInitiatingOccupancyFraction

说明老年代到百分之多少满的时候开始执行对老年代的并发垃圾回收(CMS),这个参数设置有很大技巧,基本上满足公式:

(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn

时就不会出现promotion failed。在我的应用中Xmx是6000,Xmn是500,那么Xmx-Xmn是5500兆,也就是老年代有5500兆,CMSInitiatingOccupancyFraction=90说明老年代到90%满的时候开始执行对老年代的并发垃圾回收(CMS),这时还剩10%的空间是5500*10%=550兆,所以即使Xmn(也就是年轻代共500兆)里所有对象都搬到老年代里,550兆的空间也足够了,所以只要满足上面的公式,就不会出现垃圾回收时的promotion failed;

如果按照Xmx=2048,Xmn=768的比例计算,则CMSInitiatingOccupancyFraction的值不能超过40,否则就容易出现垃圾回收时的promotion failed。

-XX:+UseCMSInitiatingOccupancyOnly

指示只有在老年代在使用了初始化的比例后 concurrent collector 启动收集

-XX:SoftRefLRUPolicyMSPerMB

相对于客户端模式的虚拟机(-client选项),当使用服务器模式的虚拟机时(-server选项),对于软引用(soft reference)的清理力度要稍微差一些。可以通过增大-XX:SoftRefLRUPolicyMSPerMB来降低收集频率。默认值是 1000,也就是说每秒一兆字节。Soft reference在虚拟机中比在客户集中存活的更长一些。其清除频率可以用命令行参数 -XX:SoftRefLRUPolicyMSPerMB=<N> 来控制,这可以指定每兆堆空闲空间的 soft reference 保持存活(一旦它不强可达了)的毫秒数,这意味着每兆堆中的空闲空间中的 soft reference 会(在最后一个强引用被回收之后)存活1秒钟。注意,这是一个近似的值,因为 soft reference 只会在垃圾回收时才会被清除,而垃圾回收并不总在发生。

-XX:LargePageSizeInBytes

内存页的大小, 不可设置过大,会影响Perm的大小。

-XX:+UseFastAccessorMethods

原始类型的快速优化,get,set 方法转成本地代码。

-XX:+DisableExplicitGC 

禁止 java 程序中的 full gc, 如 System.gc() 的调用。 最好加上防止程序在代码里误用了,对性能造成冲击。

-XX:+AggressiveHeap

特别说明下:(我感觉对于做java cache应用有帮助)

试图是使用大量的物理内存

长时间大内存使用的优化,能检查计算资源(内存, 处理器数量)

至少需要256MB内存

大量的CPU/内存, (在1.4.1在4CPU的机器上已经显示有提升)

-XX:+AggressiveOpts

加快编译

-XX:+UseBiasedLocking

锁机制的性能改善。

实战篇

   测试目的

测试被测系统使用不同的垃圾回收方案时的性能表现;

了解各种JVM参数在性能调优时的实际效果;

对遴选出的最优方案进行8小时压力测试并记录测试结果;

测试环境准备

被测程序的运行的软硬件环境:

  • D630 4G内存+T7250双核CPU+160G硬盘;

  • 操作系统:windowsXP SP3;

  • IP:11.55.15.51;

被测程序名称:

  • XXX银行采购管理系统V1.1版;

程序部署环境:

  • Tomcat6.0.18 for windows;

  • Sun JDK1.6.13 for windows;

  • Oracle10g for windows(单独运行在另外一台640M笔记本上)

性能测试工具运行的软硬件环境:

  • 操作系统:windowsxp sp3

  • 浏览器版本:IE7

  • IP地址:11.55.15.141

  • 性能测试工具:loadrunner9.10

JVM监控工具:

  • 使用Jconsole进行图形化监控;

确定JVM在被测系统的机器上最大可用内存:

通过在命令行下用 java -XmxXXXXM -version 命令反复测试发现在11.55.15.51机器上JVM能使用的最大内存为1592M。

  1. 录制测试脚本

录制前准备:修改checkcode.java文件,将随机生成的校验码改成一个固定的校验码方便脚本的自动运行,然后将编译好的checkcode.class文件替换发布包中的class文件。

选择典型业务操作进行脚本录制,每个系统的典型业务操作都会不同,需要经过分析统计,选择用户操作频率最高的部分。经过分析后确定的脚本内容为:录制系统登录操作并在登录成功后的主界面上选取一段文字作为验证点。

启动VuGen程序按脚本定义内容进行录制并调试脚本,保证脚本能正常运行。

定义测试场景

  • 虚拟用户数:30

  • 持续运行时间:8小时

  • 虚拟用户加载和卸载方式:同时

  • 性能监控指标:响应时间、吞吐量、成功交易数

  • 执行初步性能测试

使用系统默认的参数执行测试,并记录响应时间、吞吐量已经成功交易数等数据,同时监控JVM的使用情况。

  1. 选择调优方案

不同垃圾回收方法测试数据:

Id

NewRatio

SurviorRatio

TransResponse Time

Throughput

Passed Transactions

1

2

25

3.139s

3016230.514

7528

2

1

25

3.161s

2975581.301

7452

3

3

25

2.814s

3334717.818

8383

4

4

25

2.659s

3505592.450

8846

5

5

25

2.860s

3270596.069

8232

6

4

15

2.499s

3765121.986

9426

7

4

5

1.986s

4750776.581

11843

8

4

4

1.968s

4825608.161

11947

9

4

3

2.507s

3770420.243

9388

10

-XX:TargetSurvivorRatio=90 

1.924

4945053.874

12216

11

-Xmx1024M

1.903

4974137.908

12360

并发收集模式,运行时间十分钟后的对内存使用情况:

串行收集模式,运行时间十分钟:

并行收集模式,运行时间十分钟:

30-60:30个并发用户连续运行60分钟的jvm内存变化截图:

在11:36和11:56分发生了两次完全GC(Full GC),因为这时PS Old Gen已经满了,JVM自动对Old Gen中的内存进行了回收。

根据反复的测试并结合被测系统业务特点,最终敲定了使用以下最优方案进行8小时压力测试:

JAVA_OPTS=-server

-Xms1024M

-Xmx1024M

-Xmn128M

-XX:NewSize=128M

-XX:MaxNewSize=128M

-XX:SurvivorRatio=20

-XX:MaxTenuringThreshold=10

-XX:GCTimeRatio=19

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+CMSClassUnloadingEnabled

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=0

-XX:-CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=70

-XX:SoftRefLRUPolicyMSPerMB=0

–XX:PermSize=256m

-XX:MaxPermSize=256m

-Djava.awt.headless=true

  1. 调优后JVM监控图

30Vusers运行8小时截图:

测试结果分析

对于XX银行采购系统的登录操作来说,将jvm的NewRatio 和SurviorRatio设置成4时,性能表现最好!在此基础上在设置-XX:TargetSurvivorRatio=90和-Xmx1024M后性能也有一定程度的提升。

性能问题举例

性能症状

XX省一个正式上线运行的系统,每运行一段时间后程序进程会莫名其妙地被kill掉,不得不手工启动系统。

  1. 监控结果

  2. jmap命令查看堆内存分配和使用情况

./jmap -heap 31 //31为程序的进程号

Attaching to process ID 31, please wait...

Debugger attached successfully.

Server compiler detected.

JVM version is 11.0-b12 //显示jvm的版本号

using parallel threads in the new generation. //说明在年轻代使用了并行收集

using thread-local object allocation.

Concurrent Mark-Sweep GC //启用CMS收集模式

Heap Configuration:

MinHeapFreeRatio = 40

MaxHeapFreeRatio = 70 //这两项说明堆内存的使用比例在30%~60%之间

MaxHeapSize = 2147483648 (2048.0MB) //最大堆大小为2048M

NewSize = 805306368 (768.0MB)

MaxNewSize = 805306368 (768.0MB) //年轻代大小为768M

OldSize = 1342177280 (1280.0MB) //老年代代大小为1280M

NewRatio = 8 //这个有点自相矛盾,1:8

SurvivorRatio = 3 //救助区大小占整个年轻代的五分之一

PermSize = 268435456 (256.0MB) //持久代大小为256M

MaxPermSize = 268435456 (256.0MB) //持久代大小为256M

Heap Usage:

//年轻代大小,这里只计算了一个救助区,所以少了153M

New Generation (Eden + 1 Survivor Space):

capacity = 644284416 (614.4375MB)

used = 362446760 (345.65616607666016MB)

free = 281837656 (268.78133392333984MB)

56.25570803810968% used

//Eden Space大小为614.43-153=460.8M

Eden Space:

capacity = 483262464 (460.875MB)

used = 342975440 (327.0868682861328MB)

free = 140287024 (133.7881317138672MB)

70.97084204743864% used

//两个救助区的大小均为153MB, 与前面的SurvivorRatio参数设置值计算结果一致。

From Space:

capacity = 161021952 (153.5625MB)

used = 19471320 (18.569297790527344MB)

free = 141550632 (134.99320220947266MB)

12.092338813530219% used

To Space:

capacity = 161021952 (153.5625MB)

used = 0 (0.0MB)

free = 161021952 (153.5625MB)

0.0% used

//老年代大小为1280M,和根据参数配置计算的结果一致。

concurrent mark-sweep generation:

capacity = 1342177280 (1280.0MB)

used = 763110504 (727.7588882446289MB)

free = 579066776 (552.2411117553711MB)

56.85616314411163% used

//永久代大小为256M,实际使用不到50%。可在系统运行一段时间后稳定该值。

Perm Generation:

capacity = 268435456 (256.0MB)

used = 118994736 (113.48222351074219MB)

free = 149440720 (142.5177764892578MB)

44.32899355888367% used

  1. Top命令监控结果:

通过使用top命令进行持续监控发现此时CPU空闲比例为85.7%,剩余物理内存为3619M,虚拟内存8G未使用。持续的监控结果显示进程29003占用系统内存不断在增加,已经快得到最大值。

  1. Jstat命令监控结果:

使用jstat命令对PID为29003的进程进行gc回收情况检查,发现由于Old段的内存使用量已经超过了设定的80%的警戒线,导致系统每隔一两秒就进行一次FGC,FGC的次数明显多余YGC的次数,但是每次FGC后old的内存占用比例却没有明显变化—系统尝试进行FGC也不能有效地回收这部分对象所占内存。同时也说明年轻代的参数配置可能有问题,导致大部分对象都不得不放到老年代来进行FGC操作,这个或许跟系统配置的会话失效时间过长有关。

  1. Jstack打印出的堆栈内容:

在上图中发现大量的的工作流线程锁定。

在上图中发现大量的的cms线程池管理线程锁定。

原因分析

通过对jvm内存进行实时监控后发现导致老年代内存不能有效回收的原因就在于堆栈中存在大量的线程死锁问题。建议开发组认真审查com.zzxy.workflow包的源代码以及com.web.csm包中的源代码,看看是否存在线程死锁的缺陷。

该系统的JVM设置

<jvm-options>-XX:+PrintGCApplicationConcurrentTime</jvm-options> <jvm-options>-XX:+PrintGCApplicationStoppedTime</jvm-options>

<jvm-options>-XX:+PrintGCTimeStamps</jvm-options>

<jvm-options>-XX:+PrintGCDetails</jvm-options>

<jvm-options>-Xms2048m</jvm-options>

<jvm-options>-Xmx2048m</jvm-options>

<jvm-options>-server</jvm-options>

<jvm-options>-Djava.awt.headless=true</jvm-options>

<jvm-options>-XX:PermSize=256m</jvm-options>

<jvm-options>-XX:MaxPermSize=256m</jvm-options>

<jvm-options>-XX:+DisableExplicitGC</jvm-options>

<jvm-options>-Xmn768M</jvm-options>

<jvm-options>-XX:SurvivorRatio=3</jvm-options>

<jvm-options>-Xss128K</jvm-options>

<jvm-options>-XX:TargetSurvivorRatio=80</jvm-options>

<jvm-options>-XX:MaxTenuringThreshold=5</jvm-options>

<jvm-options>-XX:+UseConcMarkSweepGC</jvm-options>

<jvm-options>-XX:+CMSClassUnloadingEnabled</jvm-options>

<jvm-options>-XX:+UseCMSCompactAtFullCollection</jvm-options>

<jvm-options>-XX:-CMSParallelRemarkEnabled</jvm-options>

后记

1、性能调优要做到有的放矢,根据实际业务系统的特点,以一定时间的JVM日志记录为依据,进行有针对性的调整、比较和观察。

2、性能调优是个无止境的过程,要综合权衡调优成本和更换硬件成本的大小,使用最经济的手段达到最好的效果。

3、性能调优不仅仅包括JVM的调优,还有服务器硬件配置、操作系统参数、中间件线程池、数据库连接池、数据库本身参数以及具体的数据库表、索引、分区等的调整和优化。

4,际的调优往往是遇到瓶颈时服务频繁宕机才去排查定位解决的,不是随随便不测试就改几个jvm参数就行了。

5、通过特定工具检查代码中存在的性能问题并加以修正是一种比较经济快捷的调优方法。

  1. 附:舍得网的典型配置

$JAVA_ARGS .= " -Dresin.home=$SERVER_ROOT

-server

-Xms6000M

-Xmx6000M

-Xmn500M

-XX:PermSize=500M

-XX:MaxPermSize=500M

-XX:SurvivorRatio=65536

-XX:MaxTenuringThreshold=0

-Xnoclassgc

-XX:+DisableExplicitGC

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=0

-XX:+CMSClassUnloadingEnabled

-XX:-CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=90

-XX:SoftRefLRUPolicyMSPerMB=0

-XX:+PrintClassHistogram

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-XX:+PrintHeapAtGC

-Xloggc:log/gc.log ";
说明:

1、 -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0就是去掉了救助空间;

2、-Xnoclassgc禁用类垃圾回收,性能会高一点;

3、-XX:+DisableExplicitGC禁止System.gc(),免得程序员误调用gc方法影响性能;

4、-XX:+UseParNewGC,对年轻代采用多线程并行回收,这样收得快;

猜你喜欢

转载自blog.csdn.net/zhaofuqiangmycomm/article/details/114927455