1.JVM资料
jdk版本不高,1.8版本,使用的是cms(Concurrency Mark Sweep Garbage Collector)
#xms虚拟机最小内存 xmx虚拟机最大内存 xmn新生代初始内存(比NewRatio优先)
-Xms256m -Xmx256m -Xmn192M
#老年代和新生代比例,默认2
-XX:NewRatio=2
#禁用Survivor区自适应策略
-XX:-UseAdaptiveSizePolicy
#Survivor区使用率,默认50%
-XX:TargetSurvivorRatio=80
#调整Eden和survivor的比例,默认8,Eden占8/10,S0和S1均占1/10
-XX:SurvivorRatio=4
#设定CMS在对内存占用率达到75%的时候开始GC
-XX:CMSInitiatingOccupancyFraction=75
#只是用设定的回收阈值(上面指定的75%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.
-XX:+UseCMSInitiatingOccupancyOnly
java虚拟机监控
#java虚拟机堆内存分配情况
jmap -heap 3965
#java虚拟机垃圾回收情况
jstat -gcutil 7924 1000
#查看java进程对象内存占用排名前20
jmap -histo:live 16247 |head -n 20
#查看对象数最多的对象,按降序输出
jmap -histo pid | sort -k 2 -g -r
#查看内存的对象,按降序输出
jmap -histo pid | sort -k 3 -g -r
2.环境
腾讯云ECS 1核1G1MB环境,spring boot 2.3,上面有个java程序smartfinancialmanager.jar,主要用于信息获取并展示,未使用数据库。
3.jvm运行情况
JVM常规配置 -server -Xms256m -Xmx256m -XX:+UseSerialGC,jmap -heap 4943。
Server compiler detected.
JVM version is 25.232-b09
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 268435456 (256.0MB)
NewSize = 89456640 (85.3125MB)
MaxNewSize = 89456640 (85.3125MB)
OldSize = 178978816 (170.6875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 80543744 (76.8125MB)
used = 33062984 (31.53131866455078MB)
free = 47480760 (45.28118133544922MB)
41.049723241075064% used
Eden Space:
capacity = 71630848 (68.3125MB)
used = 24150088 (23.03131866455078MB)
free = 47480760 (45.28118133544922MB)
33.714647633377176% used
From Space:
capacity = 8912896 (8.5MB)
used = 8912896 (8.5MB)
free = 0 (0.0MB)
100.0% used
To Space:
capacity = 8912896 (8.5MB)
used = 0 (0.0MB)
free = 8912896 (8.5MB)
0.0% used
tenured generation:
capacity = 178978816 (170.6875MB)
used = 36941368 (35.23003387451172MB)
free = 142037448 (135.45746612548828MB)
20.64007843252243% used
18988 interned Strings occupying 1772640 bytes.
默认使用Mark Sweep Compact GC(标记清理压缩回收器),其中老年代与新生代的比值为默认的2,老年代占堆内存为2/3约170M,新生代占堆内存约1/3约85M。
监控java进程堆内存增长情况每隔4秒打印一次(E为新生代中的Eden区域,S0/S1为两个存活区,O为老年代),jstat -gcutil 9559 4000。
[root@VM_0_12_centos ~]# jstat -gcutil 13531 4000
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 100.00 79.78 22.95 94.28 90.40 25 0.307 2 0.192 0.499
0.00 100.00 85.61 22.95 94.28 90.40 25 0.307 2 0.192 0.499
0.00 100.00 91.45 22.95 94.28 90.40 25 0.307 2 0.192 0.499
0.00 100.00 95.33 22.95 94.28 90.40 25 0.307 2 0.192 0.499
100.00 0.00 2.13 23.89 94.29 90.43 26 0.316 2 0.192 0.509
100.00 0.00 8.02 23.89 94.29 90.43 26 0.316 2 0.192 0.509
100.00 0.00 11.94 23.89 94.29 90.43 26 0.316 2 0.192 0.509
3.分析及调优
3.1 Survivor区100%过早晋升
观察上图,可以看到S0、S1区经常为100%,这说明存活区太小了。如果为100%,每次eden区满了之后,那么从 Eden 存活下来的和原来在 Survivor 空间中不够老的对象占满 Survivor 后, 就会提升到老年代,能够看到这一轮 Minor GC 后老年代由原来的 22.95 占用变成了 23.89 占用, 这属于一个典型的 JVM 内存问题。 称为 "premature promotion"(过早晋升)。
优化:加大S0/S1空间大小。解决方法有四种:第一:机器内存够大,直接增大xms;第二:调整老年代和新生代的比例(-XX:NewRatio);第三:不调整NewRatio比例,使用xmn增大新生代大小(S0/S1随着增大);第四:调整eden和S0/S1的比例(-XX:SurvivorRatio)。
由于机器内存有限,使用后两种方法优化。先使用第二种方法,将老年代和新生代的比例由默认的2调整为1(最大只能调整到1),即新生代占xms的1/2,由于eden与S0/S1的默认比例为8,S0/S1即为xms的1/20,256/20≈12.8M。
java运行参数为:-server -Xms256m -Xmx256m -XX:NewRatio=1,运行jmap -heap 21238查看堆内存分配如下:
Server compiler detected.
JVM version is 25.232-b09
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 268435456 (256.0MB)
NewSize = 134217728 (128.0MB)
MaxNewSize = 134217728 (128.0MB)
OldSize = 134217728 (128.0MB)
NewRatio = 1
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 120848384 (115.25MB)
used = 69269144 (66.0602035522461MB)
free = 51579240 (49.189796447753906MB)
57.31904863535453% used
Eden Space:
capacity = 107479040 (102.5MB)
used = 69269144 (66.0602035522461MB)
free = 38209896 (36.439796447753906MB)
64.44897907536205% used
From Space:
capacity = 13369344 (12.75MB)
used = 0 (0.0MB)
free = 13369344 (12.75MB)
0.0% used
To Space:
capacity = 13369344 (12.75MB)
used = 0 (0.0MB)
free = 13369344 (12.75MB)
0.0% used
tenured generation:
capacity = 134217728 (128.0MB)
used = 16895496 (16.11280059814453MB)
free = 117322232 (111.88719940185547MB)
12.588125467300415% used
15914 interned Strings occupying 1560552 bytes.
从上图可以看到From Space/To Space(S0/S1)的大小为12.75M,与前面计算的12.8M近似。执行jstat -gcutil 21238 4000命令,监控堆内存变化(下图),发现S0/S1还是出现100%,这表明S0/S1区大小还是不够。
[root@VM_0_12_centos ~]# jstat -gcutil 21238 4000
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 100.00 95.38 34.40 93.66 90.09 62 0.958 3 0.248 1.206
0.00 100.00 99.36 34.40 93.66 90.09 62 0.958 3 0.248 1.206
100.00 0.00 2.08 35.99 93.66 90.09 63 0.971 3 0.248 1.219
100.00 0.00 6.06 35.99 93.66 90.09 63 0.971 3 0.248 1.219
100.00 0.00 10.04 35.99 93.66 90.09 63 0.971 3 0.248 1.219
这时,需要通过分析java程序是临时对象更多,还是长期使用的对象更多;如果程序临时新增对象更多,使用完后又销毁了,建议再增大新生代,减少老年代空间;如果认为长期使用对象更多,老年代空间大小不宜减少,则调整新生代Eden和S0/S1的比例。也可以两种方式都使用。
本程序为每隔4秒爬取其它网站信息,临时对象更多,长期使用的对象并不多,则通过增大新生代空间、减少老年代空间方法来。
当前JVM配置,分配给新生代的大小为128MB,调整新生代大小为原大小的125%,则为160M。java运行参数为:-server -Xms256m -Xmx256m -Xmn160m -XX:NewRatio=1。查看堆内存情况如下图,可以看到新生代为160MB,老年代为96MB了(正常不建议老年代比新生代小),S0/S1大小为16MB。
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 268435456 (256.0MB)
NewSize = 167772160 (160.0MB)
MaxNewSize = 167772160 (160.0MB)
OldSize = 100663296 (96.0MB)
NewRatio = 1
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 150994944 (144.0MB)
used = 71386544 (68.07951354980469MB)
free = 79608400 (75.92048645019531MB)
47.27743996514214% used
Eden Space:
capacity = 134217728 (128.0MB)
used = 71386544 (68.07951354980469MB)
free = 62831184 (59.92048645019531MB)
53.18711996078491% used
From Space:
capacity = 16777216 (16.0MB)
used = 0 (0.0MB)
free = 16777216 (16.0MB)
0.0% used
To Space:
capacity = 16777216 (16.0MB)
used = 0 (0.0MB)
free = 16777216 (16.0MB)
0.0% used
tenured generation:
capacity = 100663296 (96.0MB)
used = 13511544 (12.885612487792969MB)
free = 87151752 (83.11438751220703MB)
13.422513008117676% used
继续监控堆内存增长情况,发现S0/S1出现了91.84,不再全是100,有点效果,但还是有100和过早晋升的情况出现,这时需要不断调整新生代大小和Eden与S0/S1的比例来找到合适的配置。
[root@VM_0_12_centos smartFinancial-manager]# jstat -gcutil 7006 4000
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 91.84 52.12 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 54.78 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 57.44 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 60.10 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 64.09 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 66.75 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 69.41 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 72.07 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 74.72 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 78.71 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 81.37 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 84.03 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 86.68 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 89.34 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 93.33 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 95.99 13.42 93.66 90.05 5 0.134 2 0.100 0.233
0.00 91.84 98.65 13.42 93.66 90.05 5 0.134 2 0.100 0.233
100.00 0.00 3.04 17.33 93.39 90.08 6 0.154 2 0.100 0.253
100.00 0.00 6.13 17.33 93.39 90.08 6 0.154 2 0.100 0.253
经过不断调整和监控,在不加内存的情况下找到一种较好的配置:-server -Xms256m -Xmx256m -Xmn192m -XX:NewRatio=1 -XX:SurvivorRatio=5 -XX:TargetSurvivorRatio=90。
配置参数解释,-XX:NewRatio:老年代与新生代的比值,默认为2,即2:1,老年代占2/3,新生代占1/3;-Xmn:直接指定新生代大小,优先于NewRatio;-XX:SurvivorRatio:指定Eden区与S0/S1的比值,默认为8,即8:1,Eden区占8/10,S0占1/10,S1占1/10;-XX:TargetSurvivorRatio:指定S0/S1的利用率,默认50,即S0/S1内存占用达到50%以上时会将新生代的对象转移到老年代,调大该值可以更高效利用S0/S1的空间(也会有其它风险,自己取舍)。
最终的堆大小分配情况为:新生代总内存164MB(S0:27 S1:27 Eden:137),老年代内存64MB。程序运行正常,堆内存增长情况如下图,堆内存增长和回收达到了一种相对平衡的状态,大量的临时对象在未使用时,通过YGC就回收了,不再转移到老年代,降低了FGC的频率。
[root@VM_0_12_centos ~]# jstat -gcutil 12594 4000
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 81.08 81.76 22.80 93.89 90.17 11 0.273 2 0.165 0.438
0.00 81.08 85.62 22.80 93.89 90.17 11 0.273 2 0.165 0.438
0.00 81.08 87.54 22.80 93.89 90.17 11 0.273 2 0.165 0.438
0.00 81.08 89.47 22.80 93.89 90.17 11 0.273 2 0.165 0.438
0.00 81.08 93.33 22.80 93.89 90.17 11 0.273 2 0.165 0.438
0.00 81.08 95.27 22.80 93.89 90.17 11 0.273 2 0.165 0.438
0.00 81.08 99.13 22.80 93.89 90.17 11 0.273 2 0.165 0.438
60.70 0.00 1.76 22.80 94.27 90.26 12 0.294 2 0.165 0.460
60.70 0.00 3.42 22.80 94.27 90.26 12 0.294 2 0.165 0.460
60.70 0.00 6.69 22.80 94.27 90.26 12 0.294 2 0.165 0.460
60.70 0.00 9.96 22.80 94.27 90.26 12 0.294 2 0.165 0.460
60.70 0.00 11.60 22.80 94.27 90.26 12 0.294 2 0.165 0.460
60.70 0.00 14.88 22.80 94.27 90.26 12 0.294 2 0.165 0.460
60.70 0.00 18.15 22.80 94.27 90.26 12 0.294 2 0.165 0.460
60.70 0.00 19.79 22.80 94.27 90.26 12 0.294 2 0.165 0.460
调优技巧:通过调整新生代大小,观察FGC(全部回收)和YGC(年轻代垃圾回收)的变化,尽量使FGC增大一次时尽可能多的进行YGC,原理:很多方法中的对象都是临时创建和使用的,用完就回收了,让这些对象的回收尽量发生在YGC中,而不是FGC中,FGC的回收会影响到程序的整体运行(详细了解STW)。
3.2测试环境JVM优化
现测试环境有一个服务cronjobcloud,主要做定时任务调用,没多少业务逻辑,现发现java进程占用内存达到800MB+,进行JVM分析。
查看堆大小如下,发现新生代大小511.5M(Eden 498.5 S0/S1 6.5),老年代38.5MB;暂时感觉Eden区和S0/S1的比例有点悬殊,新生代过大,老年代过小。
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 536346624 (511.5MB)
MaxNewSize = 536870912 (512.0MB)
OldSize = 524288 (0.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 536870912 (512.0MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 536870912 (512.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 522715136 (498.5MB)
used = 91004376 (86.78853607177734MB)
free = 431710760 (411.71146392822266MB)
17.409937025431763% used
From Space:
capacity = 6815744 (6.5MB)
used = 0 (0.0MB)
free = 6815744 (6.5MB)
0.0% used
To Space:
capacity = 6815744 (6.5MB)
used = 0 (0.0MB)
free = 6815744 (6.5MB)
0.0% used
PS Old Generation
capacity = 40370176 (38.5MB)
used = 36840936 (35.134254455566406MB)
free = 3529240 (3.3657455444335938MB)
91.25780378069196% used
30164 interned Strings occupying 3328560 bytes.
查看JVM参数配置为-server -Xms512m -Xmx2048m -Xmn512m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -Xverify:none -XX:+DisableExplicitGC -Djava.awt.headless=true。
参数分析:
-XX:MetaspaceSize 设置元空间初始大小
-XX:MaxMetaspaceSize=512m 设置元空间最大可分配大小
关于元空间相关知识:
1.如果不指定元空间的大小,默认情况下,元空间最大的大小是系统内存的大小,元空间一直扩大,虚拟机可能会消耗完所有的可用系统内存。
2.如果元空间内存不够用,就会报OOM。
3.默认情况下,对应一个64位的服务端JVM来说,其默认的-XX:MetaspaceSize值为21MB,这就是初始的高水位线,一旦元空间的大小触及这个高水位线,就会触发Full GC并会卸载没有用的类,然后高水位线的值将会被重置。
4.从第3点可以知道,如果初始化的高水位线设置过低,会频繁的触发Full GC,高水位线会被多次调整。所以为了避免频繁GC以及调整高水位线,建议将-XX:MetaspaceSize设置为较高的值,而-XX:MaxMetaspaceSize不进行设置。
根据元空间相关知识,可知上述参数配置不太合理,应该只需设置XX:MetaspaceSize=256m。
-Xverify:none 表示禁用验证器。
-XX:+DisableExplicitGC 表示禁止代码中显示调用GC。
-Djava.awt.headless=true:告诉程序,现在你要工作在Headless mode(Headless模式是系统的一种配置模式,在该模式下,系统缺少了显示设备、键盘或鼠标)下,就不要指望硬件帮忙了,你得自力更生,依靠系统的计算能力模拟出这些特性来。
经过参数分析,JVM参数配置可先优化为:-server -Xms512m -Xmx2048m -Xmn512m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Xverify:none -XX:+DisableExplicitGC。
监控jvm堆内存增加情况,如下图,发现S1、老年代占用率增加过快,表明S0/S1和老年代空间太小,可能出现过早晋升和频繁FGC的情况。
[root@iZwz9dnvbin1s4bgucg80rZ bin]# jstat -gcutil 22509 4000
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 93.68 63.81 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 63.81 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 67.07 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 67.07 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 68.33 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 68.33 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 68.33 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 69.33 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 69.33 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 70.33 59.37 94.68 92.41 7 0.416 2 0.640 1.056
0.00 93.68 70.33 59.37 94.68 92.41 7 0.416 2 0.640 1.056
监控堆内存增长情况,发现出现了过早晋升的情况,如下图。
[root@iZwz9dnvbin1s4bgucg80rZ bin]# jstat -gcutil 20494 4000
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
98.00 0.00 72.01 42.18 94.18 92.36 6 0.443 1 0.411 0.854
98.00 0.00 72.01 42.18 94.18 92.36 6 0.443 1 0.411 0.854
0.00 97.09 23.42 56.81 94.81 92.69 7 0.511 2 0.794 1.305
0.00 97.09 23.86 56.81 94.81 92.69 7 0.511 2 0.794 1.305
0.00 97.09 24.67 56.81 94.81 92.69 7 0.511 2 0.794 1.305
经过不断调整新生代和老年代内存大小,找到一种较好的配置方式-server -Xms400m -Xmx2048m -Xmn300m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -Xverify:none -XX:+DisableExplicitGC。
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 314572800 (300.0MB)
MaxNewSize = 314572800 (300.0MB)
OldSize = 104857600 (100.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 134217728 (128.0MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 536870912 (512.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 260571136 (248.5MB)
used = 176894144 (168.69940185546875MB)
free = 83676992 (79.80059814453125MB)
67.88708324163733% used
From Space:
capacity = 27262976 (26.0MB)
used = 12400864 (11.826385498046875MB)
free = 14862112 (14.173614501953125MB)
45.48609806941106% used
To Space:
capacity = 26738688 (25.5MB)
used = 0 (0.0MB)
free = 26738688 (25.5MB)
0.0% used
PS Old Generation
capacity = 104857600 (100.0MB)
used = 30891864 (29.460777282714844MB)
free = 73965736 (70.53922271728516MB)
29.460777282714844% used
30540 interned Strings occupying 3234640 bytes.
JVM堆内存增长情况
45.49 0.00 99.50 29.46 94.89 93.49 12 0.491 0 0.000 0.491
45.49 0.00 99.50 29.46 94.89 93.49 12 0.491 0 0.000 0.491
45.49 0.00 99.86 29.46 94.89 93.49 12 0.491 0 0.000 0.491
45.49 0.00 99.86 29.46 94.89 93.49 12 0.491 0 0.000 0.491
45.49 0.00 100.00 29.46 94.89 93.49 12 0.491 0 0.000 0.491
45.49 0.00 100.00 29.46 94.89 93.49 12 0.491 0 0.000 0.491
45.49 0.00 100.00 29.46 94.89 93.49 12 0.491 0 0.000 0.491
0.00 94.65 4.77 34.01 94.80 92.40 13 0.504 0 0.000 0.504
0.00 94.65 6.20 34.01 94.80 92.40 13 0.504 0 0.000 0.504
0.00 94.65 7.03 34.01 94.80 92.40 13 0.504 0 0.000 0.504
使用该配置方式,新生代分片300M,老年代分配100M,对象新增和消亡能达到一种动态平衡,可充分利用内存,同时减少FGC频率,相较最初配置,节省内存约100M。坏处:由于减少了年轻代的空间大小,增加了YGC的频率(比FGC影响小)。综合考虑,在减少一定内存占用的情况下,降低了FGC频率,稍微增加了YGC频率, 总体还是比之前的配置好了不少。