性能分析的方法
自顶向下:通过软件栈顶层的应用,从上往下寻找优化机会和问题
自底向上:从软件栈底层的CPU统计数据(如CPU高速缓存未命中率)开始,逐渐上升到应用自身的结构或使用方式,从而发现优化机会
收集系统、jvm信息
- 查看进程dump
jstack pid进程id > dump.txt
- 查看IO是否有问题
iostat -dx 1
主要看%util,cpu在IO上花的资源
- 查看哪个进程IO占用高,安装IOTOP命令
yum install iotop
- 查看堆情况
jmap -heap
- jstat 使用
jstat - [-t] [-h] [ []]
参数interval、count 代表查询间隔和次数,如果省略这两个参数,说明只查询一次,假设需要每250毫秒查询一次进程5828垃圾收集状况,一共查询5次,那么命令如下
jstat -gc 5828 250 5
- -class 类加载统计信息
jstat -class 3400 1000 100
参数 | 描述 |
---|---|
Loaded | 加载class数量 |
Bytes | 所占用空间大小 |
Unloaded | 未加载数量 |
Bytes | 未加载占用空间 |
Time | 时间 |
- -gc
jstat -gc pid
参数 | 描述 |
---|---|
S0C | 第一个survivor的大小 |
S1C | 第二个survivor的大小 |
S0U | 第一个survivor的使用大小 |
S1U | 第二个survivor的使用大小 |
EC | Eden大小 |
EU | Eden使用大小 |
OC | 老年代大小 |
OU | 老年代使用大小 |
MC | 元空间大小 |
MU | 元空间使用大小 |
CCSC | 压缩类空间大小 |
CCSU | 压缩类空间使用大小 |
YGC | 年轻代垃圾回收次数 |
YGCT | 年轻代垃圾回收消耗时间 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收消耗时间 |
GCT | 垃圾回收消耗总时间 |
- -gccapacity 输出堆各个区域使用的最大和最小空间
jstat -gccapacity 3400 20 20
参数 | 描述 |
---|---|
NGCMN | 新生代最小容量 |
NGCMX | 新生代最大容量 |
NGC | 当期新生代容量 |
S0C | 第一个survivor大小 |
S1C | 第二个survivor大小 |
EC | Eden大小 |
OGCMN | 老年代最小容量 |
OGCMX | 老年代最大容量 |
OC | 当期老年代大小 |
MCMN | 最小元数据容量 |
MCMX | 最大元数据容量 |
MC | 当期元数据空间大小 |
CCSMN | 最小压缩类空间大小 |
CCSMX | 最大压缩类空间大小 |
CCSC | 当期压缩类空间大小 |
YGC | 年轻代GC次数 |
FGC | 老年代GC次数 |
- -gcutil 查看gc的统计信息,关注点主要是 已使用/总空间的占比情况
jstat -gcutil 31798
参数含义:
参数 | 描述 |
---|---|
S0 | 年轻代中第一个survivor 已使用的占当期容量百分比 |
S1 | 年轻代中第二个survivor已使用的占当期容量的百分比 |
E | 年轻代中Eden已使用占当期容量的百分比 |
O | old代已使用的占当期容量的百分比 |
M | 元空间(MetaspaceSize)已使用的占当期容量百分比 |
CCS | 压缩使用比例 |
YGC | 年轻代垃圾回收次数 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收消耗时间 |
GCT | 垃圾回收消耗总时间 |
- -gcnew 新生代垃圾回收统计
参数 | 描述 |
---|---|
S0C | 第一个survivor大小 |
S1C | 第二个survivor大小 |
S0U | 第一个survivor使用大小 |
S1U | 第二个survivor使用大小 |
TT | 对象在新生代存活次数 |
MTT | 对象在新生代存活的最大次数 |
DSS | 期望的survivor大小 |
EC | Eden大小 |
EU | Eden使用大小 |
YGC | 年轻代回收次数 |
YGCT | 年轻代回收消耗时间 |
JVM调优手段
常见配置汇总 堆设置
-Xms: 初始堆大小
-Xmx: 最大堆大小
-Xmn: 年轻代大小
-XX:NewSize=n: 设置年轻代初始大小
-XX:MaxNewSize: 设置年轻代最大值
-XX:NewRatio=n: 设置年老代和年轻代的比值,年老代/年轻代。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n: 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。Eden/S0 = Eden/S1, 如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:TargetSurvivorRatio: 设置survivor区的可使用率。当survivor的空间使用率达到这个数值时,会将对象送入老年代
-XX:MaxPermSize=n: 设置最大持久代大小
-XX:PermSize=n: 设置初始持久代大小
-XX:MinHeapFreeRatio: 设置堆空间最小空闲比例。当堆空间的空闲内存小于这个数值时,JVM便会扩展堆空间
-XX:MaxHeapFreeRatio: 设置堆空间最大空闲比例。当堆空间的空闲内存大于这个数值时,便会压缩堆控件,得到一个较小的堆
-XX:PretenureSizeThreshold: 设置大对象直接进入老年代的阈值,当对象的大小超过这个值时,将直接在老年代分配
-XX:MaxTenuringThreshold: 设置对象进入老年代的年龄的最大值。每一次Minor GC后,对象年龄加1。任何大于这个年龄的对象,一定会进入老年代
-XX:MinHeapFreeRatio:设置堆空间最小空闲比例,默认40,当堆空间的空闲内存小于这个数值时,JVM便会扩展堆空间
-XX:MaxHeapFreeRatio:设置堆空间的最大空闲比例,默认是70,当堆空间的空闲内存大于这个数值时,便会压缩堆空间,得到一个较小的堆
其他参数
-XX:CompileThreshold: JIT编译阈值,当函数的调用次数超过该值时,JIT将字节码编译成本地机器码
-XX:CITime: 打印JIT编译耗时
-XX:PrintCompilation: 打印JIT编译信息
-XX:+HeapDumpOnOutOfMemoryError 发生OOM时将堆信息保存到文件
-XX:OnOutOfMemoryError=\home 发生OOM时运行第三方脚本
-Xlog:safepoint 应用程序在GC发生时的停顿时间和应用程序在GC停顿期间的执行时间
-XX:+TraceClassLoading 跟踪类的加载信息
-XX+TraceClassUnloading 跟踪类的卸载信息
-verbose:class 同时开启类的加载信息和卸载信息
-XX:+DisableExplicitGC 禁用显示的GC操作,System.gc()不会触发Full GC
-Xnoclassgc 禁用类回收
-Xverify:none 禁用类验证
-XX:+UseCompressedOops 启用指针压缩
收集器设置
-XX:+UseSerialGC: 新生代和老年代使用串行收集器
-XX:+UseParallelGC: 新生代使用并行(Parallel Scavenge)收集器,老年代使用串行收集器
-XX:+UseParNewGC: 新生代使用并行收集器,老年代使用串行收集器
-XX:+UseParalledlOldGC: 新生代和老年代都使用并行收集器
-XX:+UseConcMarkSweepGC: 设置CMS收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
-Xlog:gc* 9以后
并行收集器设置
-XX:+UseParNewGC: 设置新生代使用并行收集器
-XX:+UseParallelOldGC: 设置老年代使用并行收集器
-XX:ParallelGCThreads=n: 设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n: 设置并行收集最大暂停时间
-XX:GCTimeRatio=n: 设置垃圾回收时间占程序运行时间的百分比,吞吐量大小,0-100之间的整数,公式为1/(1+n)
-XX:+UseAdaptiveSizePolicy: 打开自适应GC策略,新生代大小,eden和survivor的比例,晋升老年代的对象年龄等参数会自动调整,以达到堆大小,吞吐量和停顿之间的平衡点
CMS收集器设置,Java9以后已经弃用
-XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:+CMSIncrementalMode: 设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n: 设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
-XX:+CMSInitiatingOccupancyFraction: 设置CMS收集器在老年代空间被使用多少后触发,默认68%
-XX:+UseCMSCompactAtFullCollection: 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:CMSFullGCsBeforeCompaction:Java9后过期
,设置多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled: 允许对类元数据进行回收
-XX:+CMSParallelRemarkEnabled: 启用并行重标记
-XX:CMSInitiatingPermOccupancyFraction: 当永久区占用率达到这一百分比时,启动CMS回收(前提是开启-XX:+CMSClassUnloadingEnabled)
-XX:UseCMSInitiatingOccupancyOnly: 表示只在到达阈值的时候,才进行CMS回收
G1回收器设置
-XX:+UseG1GC: 使用G1回收器
-XX:+UnlockExperimentalVMOptions: 允许使用实验性参数
-XX:MaxGCPauseMillis: 设置最大垃圾收集停顿时间
-XX:GCPauseIntervalMillis: 设置停顿间隔时间
-XX:+DisableExplicitGC: 禁用显示GC
更多垃圾回收器参数,见 https://docs.oracle.com/en/java/javase/13/docs/specs/man/java.html
堆大小设置
JVM 中最大堆大小有三方面限制:
- 相关操作系统的数据模型(32-bt还是64-bit)限制;
- 系统的可用虚拟内存限制;
- 系统的可用物理内存限制。
32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
典型设置: java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
java -Xmx3550m -Xms3550m -Xss128k -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:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。
回收器选择
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。 吞吐量优先的并行收集器
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
典型配置: java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
响应时间优先的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置: java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
java -Xmx3550m -Xms3550m -Xmn2g -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前后的详细堆栈信息
输出形式:
34.702: [GC {Heap before gc invocations=7:
def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)
to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)
tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs]
-Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
调优总结
年轻代大小选择
响应时间优先的应用:
尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:
尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
响应时间优先的应用:
年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可能会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置: -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩