JVM参数调优详解
Posted 杨 戬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM参数调优详解相关的知识,希望对你有一定的参考价值。
文章目录
JVM调优
什么是JVM调优?
总的来说,JVM调优主要就是为了解决系统运行时慢、卡顿、OOM、死锁等问题。
一般包括下面的优化方向:
- 根据需求进行JVM规划和预调优(制定标准)
- 优化运行JVM运行环境(慢,卡顿)
- 解决JVM运行过程中出现的各种问题(OOM等)
为什么要JVM调优?
在堆中的储存过程如下:对象先进入Eden区,当随着程序的运行,Eden区中的内存空间即将被占满的时候,就会触发minor gc垃圾回收机制,该机制将判断Eden区中储存的所有对象,如果某一对象没有任何与之有关的引用,那么它将被删除,而那些存在引用关系的对象则将被存入To区,而在触发回收机制前S0区中存在的对象也会往后存入S1区,一但对象从Eden区进入To区,该对象的分代年龄就会从0变成1,而每进行一次minor gc回收,新生代区中的对象的分代年龄都会+1,当某个对象的分代年龄达到15的时候,这个对象就会被存入老年代区,还有一种对象会直接从Eden区进入老年代区的情况,就是该对象的大小超过To区域的一半,那么当它在Eden区中第一次遇到垃圾回收并保留下来时,就会直接进入老年代。
那么随着该程序的运行,老年代区的空间也会逐渐趋近满载,这时就会触发一个可怕的事情,full gc
回收机制,它将对新生代和老年代同时进行垃圾处理,而这时,程序请求向堆中存储对象就无法实现,这就是STW现象,程序停顿。
STW指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应, 有点像卡死的感觉,这个停顿称为STW。
Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
例如在数千万订单访问的场景中,我们的程序停顿一秒钟,就可能会造成不可想象的损失,因此JVM调优显得格外重要。
总结
根据上面的分析我们可以得出JVM调优的主要工作:
- JVM调优主要是减少GC的频率和Full GC次数,STW(stop the world)的停顿时间和次数
下面我们来详细研究一下怎么进行JVM调优?
JVM参数调优
首先我们要知道调优通过什么调?
通过设置JVM启动参数进行调优
那么下面我们来介绍一下JVM参数都有哪些:
JVM参数类型
JVM参数可以分为三类,如下表格所示:
参数 | 类型 |
---|---|
标准参数(- ) | 所有的JVM实现都必须实现这些参数的功能,而且向后兼容。 |
非标准参数(-X ) | 默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容。 |
非Stable参数(-XX ) | 此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用 |
JVM常见参数
-Xms:初始堆大小,JVM启动的时候,给定堆空间大小。
例如:-Xms3550m,设置JVM初始内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmx:最大堆大小,JVM运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
例如:-Xmx3550m:设置JVM最大可用内存为3550M。
-Xmn:设置年轻代大小。
例如:-Xmn2g:设置年轻代大小为2G。
整个堆大小 = 年轻代大小 + 老年代大小 +持久代大小。
持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss: 设置每个线程的Java栈大小。
例如:-Xss128k:设置每个线程的堆栈大小为128k。
JDK5.0以后每个线程Java栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:NewSize=n:设置年轻代大小。
-XX:NewRatio=n:设置年轻代(包括Eden和两个Survivor区)与年老代的比值。
比如设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。
注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5。
-XX:MaxPermSize=n:设置永久代大小
-XX:MaxPermSize=16m:设置持久代大小为16M。
-XX:MaxTenuringThreshold=n:设置垃圾最大年龄。
如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
其他参数如下:
JVM的参数非常之多,这里只列举比较重要的几个,通过各种各样的搜索引擎也可以得知这些信息。
参数名称 | 含义 | 默认值 | 说明 |
---|---|---|---|
-Xms | 初始堆大小 | 物理内存的1/64(<1GB) | 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.使用方法:-Xms2g 或 -XX:InitialHeapSize=2048m |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制使用方法:-Xmx2g 或 -XX:MaxHeapSize=2048m |
-Xmn | 新生代最大值(1.4or later) | 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8使用方法:-Xmn512m 或 -XX:MaxNewSize=512m | |
-XX:NewSize | 设置年轻代大小(for 1.3/1.4) | 如果我们要为 新生代分配 最小256m 的内存,最大 1024m的内存我们的参数应该这样来写: -XX:NewSize=256m -XX:MaxNewSize=1024m | |
-XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | ||
-XX:PermSize | 设置持久代(perm gen)初始值 | 物理内存的1/64 | jdk1.8已废弃 |
-XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 | jdk1.8已废弃 |
**`-XX:MetaspaceSize=N`**``**`-XX:MaxMetaspaceSize=N`** | 默认为机器的本地内存 | // 设置 Metaspace 的初始值(jdk1.8后用于替换permSize) -XX:MetaspaceSize=N // 元数据区最大值,如果不指定大小的话,虚拟机会耗尽所有可用的系统直接内存。(jdk1.8后用户替换maxPermSize) -XX:MaxMetaspaceSize=N | |
-Xss | 线程栈最大值 | JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.根据应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长)和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:-Xss is translated in a VM flag named ThreadStackSize”一般设置这个值就可以了使用方法:-Xss256k 或 -XX:ThreadStackSize=256k | |
-XX:NewRatio | 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) | -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。 | |
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 | |
-XX:+DisableExplicitGC | 关闭System.gc() | 这个参数需要严格的测试 | |
-XX:PretenureSizeThreshold | 对象超过多大是直接在旧生代分配 | 0 | 设置该参数,可以使大于这个值的对象直接在老年代分配,避免在Eden区和Survivor区发生大量的内存复制,该参数只对Serial和ParNew收集器有效,Parallel Scavenge并不认识该参数。使用方法:-XX:PretenureSizeThreshold=1000000 |
-XX:ParallelGCThreads | 并行收集器的线程数 | 此值最好配置与处理器数目相等 同样适用于CMS | |
-XX:MaxGCPauseMillis | 每次年轻代垃圾回收的最长时间(最大暂停时间) | 如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值. | |
** -XX:MaxTenuringThreshold** | 最大值15 | 15 | 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值, 则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间, 增加在年轻代即被回收的概率。该参数只有在串行GC时才有效 |
**`-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath`** | OOM时DUMP内存 | ** -XX:HeapDumpPath=你要输出的日志路径** 可以通过`jinfo -flag [+ | |
**XX:+UseSerialGC** | 年轻代使用Serial垃圾收集器 | 开启 -XX:+UseSerialGC 使用经验:不推荐使用,性能太差,老年代将会使用SerialOld垃圾收集器 -XX:+UseSerialGC | |
-XX:+UseParallelGC | 年轻代使用Parallel Scavenge垃圾收集器 | 开启 -XX:+UseParallelGC 使用经验:Linux下1.6,1.7,1.8默认开启,老年代将会使用SerialOld垃圾收集器 | |
-XX:+UseParallelOldGC | 年轻代使用Parallel Scavenge收集器 | 开启 -XX:+UseParallelOldGC 使用经验:老年代将会使用Parallel Old收集器 | |
-XX:+UseConcMarkSweepGC | 老年代使用CMS收集器(如果出现"Concurrent Mode Failure",会使用SerialOld收集器) | 开启 -XX:+UseConcMarkSweepGC 使用经验:年轻代将会使用ParNew收集器 | |
-XX:+UseG1GC | 使用G1垃圾收集器 | 使用G1垃圾收集器 | |
-XX:+CollectGen0First | 设置FullGC时是否先YGC,默认值是false | ||
-Xloggc | GC日志文件路径 | 使用方法:-Xloggc:/data/gclog/gc.log | |
-XX:+UseGCLogFileRotation | 滚动GC日志文件,须配置Xloggc | 滚动GC日志文件,须配置Xloggc | |
-XX:GCLogFileSize=100k | GC文件滚动大小,需配置UseGCLogFileRotation,设置为0表示仅通过jcmd命令触发 | ||
-XX:+PrintGCDetails | GC时打印更多详细信息,默认关闭。 开启 -XX:+PrintGCDetails 可以通过`jinfo -flag [+ | ||
-XX:+PrintGCDateStamps | GC时打印时间戳信息,默认关闭可以通过`jinfo -flag [+ | ||
-XX:+PrintTenuringDistribution | 打印存活实例年龄信息,默认关闭 | ||
-XX:+PrintGCApplicationStoppedTime | 打印应用暂停时间,默认关闭 | ||
-XX:+PrintHeapAtGC | GC前后打印堆区使用信息,默认关闭 |
JVM调优建议
年轻代大小选择
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达老年代的对象。
- 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
响应时间:从请求进入服务器到离开服务器所需的时间。
吞吐量:单位时间内处理的请求数。
老年代大小选择
-
响应时间优先的应用: 老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
- 并发垃圾收集信息
- 持久代并发收集次数
- 传统GC信息
- 花在年轻代和年老代回收上的时间比例
- 减少年轻代和老年代花费的时间,一般会提高应用的效率
-
吞吐量优先的应用: 一般吞吐量优先的应用都有一个很大的年轻代和一个较小的老年代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。
较小堆引起的碎片问题
因为老年代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对老年代进行压缩
JVM调优实战
VM 堆内存调优
对JVM进行调优,主要就是堆内存那块。所有线程共享数据区大小=新生代大小 + 老年代大小 + 永久代大小(jdk8以前)。
永久代一般固定大小为64m。所以java堆中增大年轻代后,将会减小老年代大小(因为老年代的清理是使用fullgc,所以老年代过小的话反而是会增多fullgc的)。此值对系统性能影响较大,Sun官方推荐配置为java堆的3/8 。
调整最大堆内存和最小堆内存
通过 -Xmx –Xms
:指定java堆最大值(默认值是物理内存的1/4(<1GB))和初始java堆最小值(默认值是物理内存的1/64(<1GB))
默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。简单点来说,你不停地往堆内存里面丢数据,等它剩余大小小于40%了,JVM就会动态申请内存空间不过会小于-Xmx,如果剩余大小大于70%,又会动态缩小不过不会小于–Xms。就这么简单
开发过程中,通常会将 -Xms 与 -Xmx两个参数配置成相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。
调整新生代和老年代的比值
-XX:NewRatio — 新生代(eden+2*Survivor)和老年代(不包含永久区)的比值
例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代占整个堆的1/5。在Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
整Survivor区和Eden区的比值
-XX:SurvivorRatio(幸存代)— 设置两个Survivor区和eden的比值
例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10
设置年轻代和老年代的大小
-XX:NewSize — 设置年轻代大小
-XX:MaxNewSize — 设置年轻代最大值
可以通过设置不同参数来测试不同的情况,反正最优解当然就是官方的Eden和Survivor的占比为8:1:1,然后在刚刚介绍这些参数的时候都已经附带了一些说明,感兴趣的也可以看看。反正最大堆内存和最小堆内存如果数值不同会导致多次的gc,需要注意。
总结:
根据实际事情调整新生代和幸存代的大小,官方推荐新生代占java堆的3/8,幸存代占新生代的1/10
在OOM时,记得Dump出堆,确保可以排查现场问题,通过下面命令你可以输出一个.dump文件,这个文件可以使用VisualVM或者Java自带的Java VisualVM工具。
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志路径
一般我们也可以通过编写脚本的方式来让OOM出现时给我们报个信,可以通过发送邮件或者重启程序等来解决。
GC 调优策略中很重要的一条经验总结是这样说的:
将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
JVM的栈参数调优
调整每个线程栈空间的大小
可以通过-Xss:调整每个线程栈空间的大小
JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
设置线程栈的大小: -XXThreadStackSize:
设置线程栈的大小(0 means use default stack size)
垃圾收集相关
垃圾回收器
为了提高应用程序的稳定性,选择正确的垃圾收集open in new window算法至关重要。
JVM具有四种类型的GC实现:
- 串行垃圾收集器
- 并行垃圾收集器
- CMS垃圾收集器
- G1垃圾收集器
可以使用以下参数声明这些实现:
-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseParNewGC
-XX:+UseG1GC
GC记录
为了严格监控应用程序的运行状况,我们应该始终检查JVM的垃圾回收性能。最简单的方法是以人类可读的格式记录GC活动。
使用以下参数,我们可以记录GC活动:
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=< number of log files >
-XX:GCLogFileSize=< file size >[ unit ]
-Xloggc:/path/to/gc.log
扩展阅读:JVM内部信息查看
可以使用 JVM调优中Java自带的常用的命令和可视化工具
jps:显示当前所有java进程pid的命令
jps:jps是java提供的一个显示当前所有java进程pid的命令
jps参数如下:
参数 | 说明 |
---|---|
jps –q | 只显示pid,不显示class名称,jar文件名和传递给main方法的参数 |
jps –m | 输出传递给main方法的参数,在嵌入式jvm上可能是null |
jps –l | 输出应用程序main class的完整package名或者应用程序的jar文件完整路径名 |
jps –v | 输出传递给JVM的参数 |
jps –V | 隐藏输出传递给JVM的参数 |
例如:
jstack: jstack能得到运行java程序的线程
jstack: jstack能得到运行java程序的线程(java stack和native stack)的信息
jstack 参数如下:
参数 | 说明 |
---|---|
-F | 强制dump线程堆栈信息. 用于进程hung住, jstack 命令没有响应的情况 |
-m | 同时打印java和本地(native)线程栈信息,m是mixed mode的简写 |
-l | 打印锁的额外信息 |
jmap:jmap生成堆的Dump文件/查看堆内对象实例的统计信息
jmap: jmap可以生成Java程序的堆的Dump文件,也可以查看堆内对象实例的统计信息,查看ClassLoader的信息以及finalizer队列
jstat:jstat命令查看堆内存各部分的使用量/加载类的数量
jstat jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。
基本格式:jstat options
jstat参数如下:
参数 | 说明 |
---|---|
-class | 显示ClassLoader的相关信息 |
-compiler | 显示JIT编译的相关信息 |
-gc | 显示与GC相关信息 |
-gccapacity | 显示各个代的容量和使用情况 |
-gccause | 显示垃圾收集相关信息(同-gcutil),同时显示最后一次或当前正在发生的垃圾收集的诱发原因 |
-gcnew | 显示新生代信息 |
-gcnewcapacity | 显示新生代大小和使用情况 |
-gcold | 显示老年代信息 |
-gcoldcapacity | 显示老年代大小 |
-gcpermcapacity | 显示永久代大小 |
-gcutil | 显示垃圾收集信息 |
-printcompilation | 输出JIT编译的方法信息 |
-t | 在输出信息前加上一个Timestamp列,显示程序的运行时间 |
-h | 可以在周期性数据输出后,输出多少行数据后,跟着一个表头信息 |
interval | 用于指定输出统计数据的周期,单位为毫秒 |
count | 用于指定一个输出多少次数据 |
jinfo:jinfo查看正在运行的java程序的扩展参数
jinfo:jinfo可以用来查看正在运行的java程序的扩展参数,甚至支持运行时,修改部分参数
参数 | 说明 |
---|---|
-flag <name> | 以打印命名 VM 标志的值 |
`-flag [+ | -]` |
-flag <name>=<value> | 将命名的 VM 标志设置为给定值 |
-flags | 以打印虚拟机标志 |
-sysprops | 打印 Java 系统属性 |
<no option> | 打印虚拟机标志和系统属性 |
jvisualvm 可视化分析工具
VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。在JDK_HOME/bin(默认是C:\\Program Files\\Java\\jdk1.6.0_13\\bin)目录下面,有一个jvisualvm.exe文件,双击打开,从UI上来看,这个软件是基于NetBeans开发的了。
VisualVM 提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于 Java 技术的应用程序的详细信息。VisualVM 对 Java Development Kit (JDK) 工具所检索的 JVM 软件相关数据进行组织,并通过一种使您可以快速查看有关多个 Java 应用程序的数据的方式提供该信息。您可以查看本地应用程序或远程主机上运行的应用程序的相关数据。此外,还可以捕获有关 JVM 软件实例的数据,并将该数据保存到本地系统,以供后期查看或与其他用户共享。
双击启动 jvisualvm.exe,启动起来后和jconsole 一样同样可以选择本地和远程,如果需要监控远程同样需要配置相关参数。
搜一下就能找到
点击打开
可以自行学习一下
以上是关于JVM参数调优详解的主要内容,如果未能解决你的问题,请参考以下文章