1.JVM参数
1.1. 通用JVM参数
-server
如果不配置该参数,JVM会根据应用服务器硬件配置自动选择不同模式,server模式启动比较慢,但是运行期速度得到了优化,适合于服务器端运行的JVM。
-client
启动比较快,但是运行期响应没有server模式的优化,适合于个人PC的服务开发和测试。jdk6之后默认server模式。
-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收集将直接将其转移到老年代中,这将加快老年代的空间使用速度,引发频繁的完全垃圾回收。
-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%。
-Xss
设置每个线程的堆栈大小,根据应用的线程所需内存大小进行调整,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。当这个选项被设置的较大(>2MB)时将会在很大程度上降低系统的性能。因此在设置这个值时应该格外小心,调整后要注意观察系统的性能,不断调整以期达到最优。
JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。
-Xnoclassgc
这个选项用来取消系统对特定类的垃圾回收。它可以防止当这个类的所有引用丢失之后,这个类仍被引用时不会再一次被重新装载,因此这个选项将增大系统堆内存的空间。禁用类垃圾回收,性能会高一点;
1.2. 串行收集器参数
-XX:+UseSerialGC:
设置串行收集器 。
1.3 并行收集器参数
-XX:+UseParallelGC:
选择垃圾收集器为并行收集器,此配置仅对年轻代有效,即上述配置下,年轻代使用并行收集,而老年代仍旧使用串行收集。采用了多线程并行管理和回收垃圾对象,提高了回收效率,提高了服务器的吞吐量,适合于多处理器的服务器。
-XX:ParallelGCThreads
配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
-XX:+UseParallelOldGC:
采用对于老年代并发收集的策略,可以提高收集效率。JDK6.0支持对老年代并行收集。
-XX:MaxGCPauseMillis
设置每次年轻代并行收集最大暂停时间,如果无法满足此时间,JVM会自动调整年轻代大小以满足此值。
-XX:+UseAdaptiveSizePolicy:
设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
1.4 并发收集器参数
-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:+DisableExplicitGC
禁止 java 程序中的 full gc, 如 System.gc() 的调用。 最好加上防止程序在代码里误用了,对性能造成冲击。
2 监控工具(jdk的bin目录)


3.性能问题案例
XX省一个正式上线运行的系统,每运行一段时间后程序进程会莫名其妙地被kill掉,不得不手工启动系统。
3.1监控结果
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
Top命令监控结果

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

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

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

在上图中发现大量的的cms线程池管理线程锁定。
3.2 原因分析
通过对jvm内存进行实时监控后发现导致老年代内存不能有效回收的原因就在于堆栈中存在大量的线程死锁问题。建议开发组认真审查com.xxx.workflow包的源代码以及com.web.csm包中的源代码,看看是否存在线程死锁的缺陷。
4.经验总结
- 性能调优要做到有的放矢,根据实际业务系统的特点,以一定时间的JVM日志记录为依据,进行有针对性的调整、比较和观察。
- 性能调优是个无止境的过程,要综合权衡调优成本和更换硬件成本的大小,使用最经济的手段达到最好的效果。
- 性能调优不仅仅包括JVM的调优,还有服务器硬件配置、操作系统参数、中间件线程池、数据库连接池、数据库本身参数以及具体的数据库表、索引、分区等的调整和优化。
- 通过特定工具检查代码中存在的性能问题并加以修正是一种比较经济快捷的调优方法。
常见配置:
并行收集器主要以到达一定的吞吐量为目标,适用于科学计算和后台处理等。
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
使用ParallelGC作为并行收集器, GC线程为20(CPU核心数>=20时),内存问题根据硬件配置具体提供。建议使用物理内存的80%左右作为JVM内存容量。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
指定老年代收集器,在JDK5.0之后的版本,ParallelGC对应的全收集器就是ParallelOldGC。可以忽略
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
指定GC时最大暂停时间。单位是毫秒。每次GC最长使用100毫秒。可以尽可能提高工作线程的执行资源。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
UseAdaptiveSizePolicy是提高年轻代GC效率的配置。次收集器执行效率。
并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域、互联网领域等。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
指定年轻代收集器为ParNew,年老代收集器ConcurrentMarkSweep,并发GC线程数为20(CPU核心>=20),并发GC的线程数建议使用(CPU核心数+3)/4或CPU核心数【不推荐使用】。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
CMSFullGCsBeforeCompaction=5执行5次GC后,运行一次内存的整理。
UseCMSCompactAtFullCollection执行老年代内存整理。可以避免内存碎片,提高GC过程中的效率,减少停顿时间。
简单总结
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代存放长期存活对象。
较小堆引起的碎片问题,因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、整理方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
2272

被折叠的 条评论
为什么被折叠?



