1.JVM资料
java虚拟机设置
#xms虚拟机最小内存 xmx虚拟机最大内存 xmn新生代初始内存(比NewRatio优先)
-Xms256m -Xmx256m -Xmn192M
#老年代和新生代比例,默认2
-XX:NewRatio=2
#禁用Survivor区自适应策略
-XX:-UseAdaptiveSizePolicy
#Survivor区使用率,默认50%
-XX:TargetSurvivorRatio=80
#调整Eden和survivor的比例,默认8
-XX:SurvivorRatio=4
#设定CMS在对内存占用率达到70%的时候开始GC
-XX:CMSInitiatingOccupancyFraction=75
#只是用设定的回收阈值(上面指定的70%),如果不指定,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空间大小。解决方法有3种:第一:机器内存够大,直接增大xms;第二:调整老年代和新生代的比例(-XX:NewRatio);第三:不调整NewRatio比例,增大新生代大小(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。TargetSurvivorRatio表示S0/S1的使用率达到90%以上时才将新生代的对象转移到老年代,调大该值可以更高效利用S0/S1的空间(也会有其它风险,自己取舍)。
最终的堆大小分配情况为:新生代总内存164MB(S0:27 S1:27 Eden:137),老年代内存64MB。程序运行正常,堆内存增长情况如下,内存增长和回收达到了一种相对平衡的状态,大量的临时对象在未使用时,通过YGC就回收了,未再转移到老年代。
[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)。