JVM调优参数详解
GC有两种类型:Scavenge GC 和Full GC
1、Scavenge GC
⼀般情况下,当新对象⽣成,并且在Eden申请空间失败时,就会触发Scavenge GC,堆的Eden区域进⾏GC,清除⾮存活对象,并且把尚且存活的对象移动到Survivor的两个区中。
2、Full GC
对整个堆进⾏整理,包括Young、Tenured和Perm。Full GC ⽐Scavenge GC要慢,因此应该尽可能减少Full GC,有如下原因可能导致Full GC
a、Tenured被写满;
b、Perm域被写满
c、()被显⽰调⽤
d、上⼀次GC之后Heap的各域分配策略动态变化;
-Xmx512m -Xms512m -Xmn192m -Xss128k
JVM中最⼤堆⼤⼩受三⽅⾯限制,相关操作系统的数据模型(32位还是64位)限制;系统的可⽤虚拟内存限制;系统的可⽤物理内存限制-Xmx512m:
设置JVM实例堆最⼤可⽤内存为512M。
-Xms512m:
设置JVM促使内存为512m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn192m
设置年轻代⼤⼩为192m。整个JVM内存⼤⼩=年轻代⼤⼩ + 年⽼代⼤⼩ + 持久代⼤⼩。持久代⼀般固定⼤⼩为64m,所以增⼤年轻代后,将会减⼩年⽼代⼤⼩。此值对系统性能影响较⼤,Sun官⽅推荐配置为整个堆的3/8。
-Xss128k
设置每个线程的堆栈⼤⼩。JDK5.0以后每个线程堆栈⼤⼩为1M,以前每个线程堆栈⼤⼩为256K。更具应⽤的线程所需内存⼤⼩进⾏调整。在相同物理内存下,减⼩这个值能⽣成更多的线程。但是操作系统对⼀个进程内的线程数还是有限制的,不能⽆限⽣成,经验值在3000~5000左右。
注意下⾯问题:
(1)增加Heap的⼤⼩虽然会降低GC的频率,但也增加了每次GC的时间。并且GC运⾏时,所有的⽤户线程将暂停,也就是GC期
间,Java应⽤程序不做任何⼯作。
(2)Heap⼤⼩并不决定进程的内存使⽤量。进程的内存使⽤量要⼤于-Xmx定义的值,因为Java为其他任务分配内存,例如每个线程的Stack等。
(3)Server端JVM最好将-Xms和-Xmx设为相同值。为了优化GC,最好让-Xmn值约等于-Xmx的1/3(也有指出为3/8)。
(4)⼀个应⽤程序最好是每10到20秒间运⾏⼀次GC,每次在半秒之内完成。
java  -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4
设置年轻代(包括Eden和两个Survivor区)与年⽼代的⽐值(除去持久代)。设置为4,则年轻代与年⽼代所占⽐值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4
设置年轻代中Eden区与Survivor区的⼤⼩⽐值。设置为4,则两个Survivor区与⼀个Eden区的⽐值为2:4,⼀个Survivor区占整个年轻代的1/6
-XX:PermSize=128M
设置持久代⼤⼩为128M
-XX:MaxPermSize=16m
设置持久代最⼤为16m。
MaxPermSize过⼩会导致:java.lang.OutOfMemoryError: PermGen space
-XX:MaxTenuringThreshold=0
设置垃圾最⼤年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进⼊年⽼代。对于年⽼代⽐较多的应⽤,可以提⾼效率。如果将此值设置为⼀个较⼤值,则年轻代对象会在Survivor区进⾏多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
JVM给了三种选择:
串⾏收集器
使⽤单线程处理所有垃圾回收⼯作,因为⽆需多线程交互,所以效率⽐较⾼,但是也⽆法使⽤多处理器的优势,所以此收集器使⽤单处理器机器。当然此收集器也可以⽤在⼩数据量(100M)情况下的多处理器机器上,可以使⽤-XX:+UseSericalGC打开适⽤情况:数据量⽐较⼩(100M左右);单处理器下并对相应时间⽆要求的应⽤
缺点:只能⽤于⼩型应⽤
并⾏收集器
对年轻代进⾏并⾏垃圾回收,因此可以减少垃圾回收时间,⼀般在多线程处理机器上使⽤。在Java SE6.0中进⾏了增强,可以在年⽼代进⾏并⾏收集,如果年⽼代不使⽤并发收集的话,使⽤单线程进⾏垃圾回收,因此会制约扩展能⼒,使⽤-XX:+UserParallelOldGC打开
-XX:ParallelGCThreads=N,设置并⾏垃圾回收的线程数,此值可以设置与机器处理机数量⼀致;
使⽤情况:“对吞吐量有⾼要求”,多CPU,对应⽤时间⽆要求的中、⼤型应⽤。如后台处理、科学计算
缺点:应⽤相应时间可能较长;
并发收集器
可以保证⼤部分⼯作都并发进⾏(应⽤不停⽌),垃圾回收只暂停很少时间,此收集器适合对相应时间要求⽐较较⾼的中、⼤规模应⽤。
使⽤-XX:+UseGoncMarkSweepGC打开
适⽤情况:“对响应时间有⾼要求”,多CPU,对应⽤响应时间有较⾼要求的中、⼤型应⽤。如:Web服务器/应⽤服务器、电信交换、集成开发环境
但是串⾏收集器只适⽤于⼩数据量的情况,所以这⾥的选择主要针对并⾏收集器和并发收集器。
默认情况下,JDK5.0以前都是使⽤串⾏收集器,如果想使⽤其他收集器需要在启动时加⼊相应参数。JDK5.0以后,JVM会根据当前系统配置进⾏判断。
吞吐量优先的并⾏收集器
如上⽂所述,并⾏收集器主要以到达⼀定的吞吐量为⽬标,适⽤于科学技术和后台处理等。
典型配置:
java -Xmx3800m -Xms3800m -Xmn192m -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC
选择垃圾收集器为并⾏收集器。此配置仅对年轻代有效。即上述配置下,年轻代使⽤并发收集,⽽年⽼代仍旧使⽤串⾏收集。
-XX:ParallelGCThreads=20
配置并⾏收集器的线程数,即:同时多少个线程⼀起进⾏垃圾回收。此值最好配置与处理器数⽬相等。
java -Xmx512m -Xms512m -Xmn192m -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC
配置年⽼代垃圾收集⽅式为并⾏收集。JDK6.0⽀持对年⽼代并⾏收集。
java -Xmx512m -Xms512m -Xmn192m -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100
设置每次年轻代垃圾回收的最长时间,如果⽆法满⾜此时间,JVM会⾃动调整年轻代⼤⼩,以满⾜此值。
如果指定了此值的话,堆⼤⼩和垃圾回收相关参数会进⾏调整以达到指定值,设定辞职可能会减少应⽤的吞吐量。
java -Xmx512m -Xms512m -Xmn192m -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy
设置此选项后,并⾏收集器会⾃动选择年轻代区⼤⼩和相应的Survivor区⽐例,以达到⽬标系统规定的最低相应时间或者收集频率等,此值建议使⽤并⾏收集器时,⼀直打开。
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运⾏时间的百分⽐。公式为1/(1+n)
吞吐量,吞吐量为垃圾回收时间与⾮垃圾回收时间的⽐值。-XX:GCTimeRatio=19时,表⽰5%的时间⽤于垃圾回收,默认情况99,即1%的时间⽤于垃圾回收
响应时间优先的并⾏收集器
如上⽂所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适⽤于应⽤服务器、电信领域等。
典型配置:
java -Xmx512m -Xms512m -Xmn192m -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
设置年⽼代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代⼤⼩最好⽤-Xmn设置。-XX:+UseParNewGC
设置年轻代为并⾏收集。可与CMS收集同时使⽤。JDK5.0以上,JVM会根据系统配置⾃⾏设置,所以⽆需再设置此值。
java -Xmx512m -Xms512m -Xmn192m -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -
XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction
由于并发收集器不对内存空间进⾏压缩、整理,所以运⾏⼀段时间以后会产⽣“碎⽚”,使得运⾏效率降低。此值设置运⾏多少次GC以后对内存空间进⾏压缩、整理。
-XX:+UseCMSCompactAtFullCollection
打开对年⽼代的压缩。可能会影响性能,但是可以消除碎⽚
辅助信息
JVM提供了⼤量命令⾏参数,打印信息,供调试使⽤。主要有以下⼀些:
-XX:+PrintGC
输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails
输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K-
>10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上⾯两个混合使⽤
输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执⾏时间。可与上⾯混合使⽤
输出形式:Application time: 0.5291524 seconds
-
XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上⾯混合使⽤
输出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
常见配置汇总
堆设置
-Xms:初始堆⼤⼩
-Xmx:最⼤堆⼤⼩
-XX:NewSize=n:设置年轻代⼤⼩
-XX:NewRatio=n:设置年轻代和年⽼代的⽐值。如:为3,表⽰年轻代与年⽼代⽐值为1:3,年轻代占整个年轻代年⽼代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的⽐值。注意Survivor区有两个。如:3,表⽰Eden:Survivor=3:2,⼀个Survivor 区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代⼤⼩
收集器设置
-XX:+UseSerialGC:设置串⾏收集器
-XX:+UseParallelGC:设置并⾏收集器
-XX:+UseParalledlOldGC:设置并⾏年⽼代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并⾏收集器设置
-XX:ParallelGCThreads=n:设置并⾏收集器收集时使⽤的CPU数。并⾏收集线程数。
-XX:MaxGCPauseMillis=n:设置并⾏收集最⼤暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运⾏时间的百分⽐。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适⽤于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集⽅式为并⾏收集时,使⽤的CPU数。并⾏收集线程数。
JVM调优⼯具Jconsole,jProfile,VisualVM
Jconsole :JDK⾃带,功能简单,但是可以在系统有⼀定负荷的情况下使⽤。对垃圾回收算法有很详细的跟踪。
JProfiler:商业软件,需要付费。功能强⼤。
VisualVM:JDK⾃带,功能强⼤,与JProfiler类似。推荐
内存泄漏检查
内存泄漏是⽐较常见的问题,⽽且解决⽅法也⽐较通⽤,这⾥可以重点说⼀下,⽽线程、热点⽅⾯的问题则是具体问题具体分析了。内存泄漏⼀般可以理解为系统资源(各⽅⾯的资源,堆、栈、线程等)在错误使⽤的情况下,导致使⽤完毕的资源⽆法回收(或没有回收),从⽽导致新的资源分配请求⽆法完成,引起系统错误。内存泄漏对系统危害⽐较⼤,因为他可以直接导致系统的崩溃。需要区别⼀下,内存泄漏和系统超负荷两者是有区别的,虽然可能导致的最终结果是⼀样的。内存泄漏是⽤完的资源没有回收引起错误,⽽系统超负荷则是系统确实没有那么多资源可以分配了(其他的资源都在使⽤)。
调优总结
年轻代⼤⼩选择
响应时间优先的应⽤:
尽可能设⼤,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发⽣的频率也是最⼩的。同时,减少到达年⽼代的对象。
吞吐量优先的应⽤:
尽可能的设置⼤,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并⾏进⾏,⼀般适合8CPU以上的应⽤。
年⽼代⼤⼩选择
响应时间优先的应⽤:
年⽼代使⽤并发收集器,所以其⼤⼩需要⼩⼼设置,⼀般要考虑并发会话率和会话持续时间等⼀些参数。如果堆设置⼩了,可以会造成内存碎⽚、⾼回收频率以及应⽤暂停⽽使⽤传统的标记清除⽅式;如果堆⼤了,则需要较长的收集时间。最优化的⽅案,⼀般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年⽼代回收上的时间⽐例jvm调优参数
减少年轻代和年⽼代花费的时间,⼀般会提⾼应⽤的效率
吞吐量优先的应⽤