浅谈JVM的优化
前言
前面我们了解过JVM中堆的GC分代回收机制,在运行Java程序时,我们可以使用JVM的参数对程序的执行过程进行优化,以达到更优的内存配置和GC配置,从而提高程序的性能和稳定性。
JVM的参数
JVM的参数分为三种:
标准参数
非标准参数
非稳定参数
标准参数以-开头,如:java -version、java -jar等,通过java -help可以查询所有的标准参数,本章不具体讨论。
非标准参数以-X开头,是标准参数的扩展,输入:java -X可以查询所有非标准参数。我们可以通过设置非标准参数来配置堆的内存分配,常用的非标准参数有:
-Xmn新生代内存的最大值,包括Eden区和两个Survivor区的总和,写法如:-Xmn1024,-Xmn1024k,-Xmn1024m,-Xmn1g 。
-Xms堆内存的最小值,默认值是总内存/64(且小于1G),默认情况下,当堆中可用内存小于40%(这个值可以用-XX: MinHeapFreeRatio 调整,如-X:MinHeapFreeRatio=30)时,堆内存会开始增加,一直增加到-Xmx的大小。
-Xmx堆内存的最大值,默认值是总内存/64(且小于1G),如果Xms和Xmx都不设置,则两者大小会相同,默认情况下,当堆中可用内存大于70%时,堆内存会开始减少,一直减小到-Xms的大小;
整个堆的大小=年轻代大小+年老代大小,堆的大小不包含持久代大小,如果增大了年轻代,年老代相应就会减小,官方默认的配置为年老代大小/年轻代大小=2/1左右;
建议在开发测试环境可以用Xms和Xmx分别设置最小值最大值,但是在线上生产环境,Xms和Xmx设置的值必须一样,原因与年轻代一样——防止抖动;
-Xss每个线程的栈内存,默认1M,一般来说是不需要改的。
-Xrs减少JVM对操作系统信号的使用。
-Xprof跟踪正运行的程序,并将跟踪数据在标准输出输出;适合于开发环境调试。
-Xnoclassgc关闭针对class的gc功能;因为其阻止内存回收,所以可能会导致OutOfMemoryError错误,慎用;
-Xincgc开启增量gc(默认为关闭);这有助于减少长时间GC时应用程序出现的停顿;但由于可能和应用程序并发执行,所以会降低CPU对应用的处理能力。
-Xloggc:file与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。
非稳定参数,以-XX开头,官方介绍这些参数是不稳定的,但往往在进行JVM优化时,这些参数能起到重要作用。
非稳定参数分为三类:
性能参数:用于JVM的性能调优和内存分配控制,如内存大小的设置;
行为参数:用于改变JVM的基础行为,如GC的方式和算法的选择;
调试参数:用于监控、打印、输出jvm的信息;
使用方法有三种:
-XX:+<选项> 启用选项
-XX:-<选项> 不启用选项
-XX:<选项>=<数值> 给选项设置一个数字类型值,如 32k, 1024m, 2g
-XX:<选项>=<字符串> 给选项设置一个字符串值,如
-XX:HeapDumpPath=./dump.core
常用性能参数:
参数及其默认值 | 描述 |
-XX:NewSize=2.125m | 新生代对象生成时占用内存的默认值 |
-XX:MaxNewSize=size | 新生成对象能占用内存的最大值 |
-XX:MaxPermSize=64m | 方法区所能占用的最大内存(非堆内存) |
-XX:PermSize=64m | 方法区分配的初始内存 |
-XX:MaxTenuringThreshold=15 | 对象在新生代存活区切换的次数(坚持过MinorGC的次数,每坚持过一次,该值就增加1),大于该值会进入老年代 |
-XX:MaxHeapFreeRatio=70 | GC后java堆中空闲量占的最大比例,大于该值,则堆内存会减少 |
-XX:MinHeapFreeRatio=40 | GC后java堆中空闲量占的最小比例,小于该值,则堆内存会增加 |
-XX:NewRatio=2 | 新生代内存容量与老生代内存容量的比例 |
-XX:ReservedCodeCacheSize= 32m | 保留代码占用的内存容量 |
-XX:ThreadStackSize=512 | 设置线程栈大小,若为0则使用系统默认值 |
-XX:LargePageSizeInBytes=4m | 设置用于Java堆的大页面尺寸 |
-XX:PretenureSizeThreshold= size | 大于该值的对象直接晋升入老年代(这种对象少用为好) |
-XX:SurvivorRatio=8 | Eden区域Survivor区的容量比值,如默认值为8,代表Eden:Survivor1:Survivor2=8:1:1 |
常用行为参数:
参数及其默认值 | 描述 |
-XX:-UseSerialGC | 启用串行GC,即采用Serial+Serial Old模式 |
-XX:-UseParallelGC | 启用并行GC,即采用Parallel Scavenge+Serial Old收集器组合(-Server模式下的默认组合) |
-XX:GCTimeRatio=99 | 设置用户执行时间占总时间的比例(默认值99,即1%的时间用于GC) |
-XX:MaxGCPauseMillis=time | 设置GC的最大停顿时间(这个参数只对Parallel Scavenge有效) |
-XX:+UseParNewGC | 使用ParNew+Serial Old收集器组合 |
-XX:ParallelGCThreads | 设置执行内存回收的线程数,在+UseParNewGC的情况下使用 |
-XX:+UseParallelOldGC | 使用Parallel Scavenge +Parallel Old组合收集器 |
-XX:+UseConcMarkSweepGC | 使用ParNew+CMS+Serial Old组合并发收集,优先使用ParNew+CMS,当用户线程内存不足时,采用备用方案Serial Old收集。 |
-XX:-DisableExplicitGC | 禁止调用System.gc();但jvm的gc仍然有效 |
-XX:+ScavengeBeforeFullGC | 新生代GC优先于Full GC执行 |
常用调试参数:
参数及其默认值 | 描述 |
-XX:-CITime | 打印消耗在JIT编译的时间 |
-XX:ErrorFile=./hs_err_pid<pid>.log | 保存错误日志或者数据到文件中 |
-XX:-ExtendedDTraceProbes | 开启solaris特有的dtrace探针 |
-XX:HeapDumpPath=./java_pid<pid>.hprof | 指定导出堆信息时的路径或文件名 |
-XX:-HeapDumpOnOutOfMemoryError | 当首次遭遇OOM时导出此时堆中相关信息 |
-XX:OnError=”<cmd args>;<cmd args>” | 出现致命ERROR之后运行自定义命令 |
-XX:OnOutOfMemoryError=”<cmd args>;<cmd args>” | 当首次遭遇OOM时执行自定义命令 |
-XX:-PrintClassHistogram | 遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同 |
-XX:-PrintConcurrentLocks | 遇到Ctrl-Break后打印并发锁的相关信息,与jstack -l功能相同 |
-XX:-PrintCommandLineFlags | 打印在命令行中出现过的标记 |
-XX:-PrintCompilation | 当一个方法被编译时打印相关信息 |
-XX:-PrintGC | 每次GC时打印相关信息 |
-XX:-PrintGC Details | 每次GC时打印详细信息 |
-XX:-PrintGCTimeStamps | 打印每次GC的时间戳 |
-XX:-TraceClassLoading | 跟踪类的加载信息 |
-XX:-TraceClassLoadingPreorder | 跟踪被引用到的所有类的加载信息 |
-XX:-TraceClassResolution | 跟踪常量池 |
-XX:-TraceClassUnloading | 跟踪类的卸载信息 |
-XX:-TraceLoaderConstraints | 跟踪类加载器约束的相关信息 |
常见的配置
典型的JVM配置:
java -Xmx2048m -Xms2048m -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
参数说明:
-Xmx2048m :堆的最大内存为2048M。
-Xms2048m :堆的最小内存为2048m。最大内存和最小内存配置相同,以避免每次垃圾回收完成后JVM重新分配内存也就是抖动。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比例(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比例。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
GC的选择
JVM中有三种垃圾收集器:
串行收集器
性能不高,只适合数据不多的应用
并行收集器
能达到较高的吞吐量,适用于科学技术和后台处理等
并发收集器
能保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等
典型的JVM配置
1)
java -Xmx2048m -Xms2048m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
说明:
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
2)
java -Xmx2048m -Xms2048m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。
-XX:+UseParNewGC: 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
调优总结
响应时间优先的应用
将年轻代尽量设置大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。
吞吐量优先的应用
将年轻代尽设置大量,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。