前言
新系统上线,由于测试环境机器配置太低,所以单独找了一台预发机做接口压测,但是QPS达到30时候cpu就满了,简直慌了,新系统这么垃圾的么~
背景介绍
新的账务系统,角色定位是内部户机构账,所以根据不同的金融交易场次会有一次记账请求中存在多借多贷,也就是一次请求会出现给1~8个账户在一个事务中做记账处理,可想这里面会有多少要注意的坑,详细的不做展开~
找问题
top
命令
top -Hp pid
发现下面均是某个进程cpu使用率高
找到该pid下cpu占用最高的tid,然后通过 jstack pid | grep tid -A 30 找到对应的进程信息
printf '%x\n' 12149
tid 转换16进制
jstack pid | grep tid
查看该线程信息。好了,不用往下瞅了,FullGC的进程~
jstat -gcutil pid 1000 10
然后稀里糊涂的把堆详细信息都给打出来了~,看下依旧不是这块问题(使用率确实非常低)。
jmap -heap pid
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 235929600 (225.0MB)
MaxNewSize = 235929600 (225.0MB)
OldSize = 837812224 (799.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 134217728 (128.0MB)
CompressedClassSpaceSize = 125829120 (120.0MB)
MaxMetaspaceSize = 134217728 (128.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 212336640 (202.5MB)
used = 0 (0.0MB)
free = 212336640 (202.5MB)
0.0% used
Eden Space:
capacity = 188743680 (180.0MB)
used = 0 (0.0MB)
free = 188743680 (180.0MB)
0.0% used
From Space:
capacity = 23592960 (22.5MB)
used = 0 (0.0MB)
free = 23592960 (22.5MB)
0.0% used
To Space:
capacity = 23592960 (22.5MB)
used = 0 (0.0MB)
free = 23592960 (22.5MB)
0.0% used
concurrent mark-sweep generation:
capacity = 837812224 (799.0MB)
used = 95685920 (91.25320434570312MB)
free = 742126304 (707.7467956542969MB)
11.420926701589877% used
41909 interned Strings occupying 5041832 bytes.
还在一脸懵逼 ··· ···
这时候有个小伙伴提到了元空间内存满了,恍惚,回过头去查了下 M、CCS 明明是93.19%、90.48% 怎么就满了呢??
那么jstat -gcutil 命令展示列的 M、CCS 到底是啥? 好吧把定义贴出来:
column {
header "^M^" /* Metaspace - Percent Used */
data (1-((sun.gc.metaspace.capacity - sun.gc.metaspace.used)/sun.gc.metaspace.capacity)) * 100
align right
width 6
scale raw
format "0.00"
}
column {
header "^CCS^" /* Compressed Class Space - Percent Used */
data (1-((sun.gc.compressedclassspace.capacity - sun.gc.compressedclassspace.used)/sun.gc.compressedclassspace.capacity)) * 100
align right
width 6
scale raw
format "0.00"
}
也就是说M是表示metaspace的使用百分比??事实证明并非如此~
通过jstat -gccapacity pid 看下具体的metaspace空间值,如下图:
jstat -gccapacity pid
MC空间大小是131072KB=128M 然后通过 jstat -gc pid 发现 MC(元空间大小)、MU(元空间使用大小)两列的值居然都不是是131072(此处没截图)。
说明:下面两张图是预发环境配置修改后的数值,跟当时定位问题时候数值不一样,此处为了说明 MC\MU并不是jdk的环境变量配置的MetaspaceSize 。并且使用jstat -gcutil执行出来的结果M 的百分比并不代表触发 FullGC的阈值~
后来通过网上找到说jdk的环境变量的配置-XX:MetaspaceSize=128m 这才是触发元空间FullGC的条件,对比了下线上分配百分比,所以先找到运维把预发环境的环境变量-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
部分 都配置成256m(至于为啥是256,明天可以根环境据修改后的压测分时数据再做下说明),按照原来的并变量发数再压一次, 果然没有再出现堆使用率很低并且还在不配置停FullGC的现象。
的
##含义## 表面上这个问题到一段落,接下来还有针对程序的局部调优,等翠花上菜!!!
那顺便看下,为什么把jdk1.7里面的永久代Perm 换成了1.8里面的元空间Metaspace呢
在 HotSpot 虚拟机、Java7 版本中已经将永久代的静态变量和运行时常量池转移到了堆中,其余部分则存储在 JVM 的非堆内存中,而 Java8 版本已经将方法区中实现的永久代去掉了,并用元空间(class metadata 主要存储:方法、字段、类的描述信息等)代替了之前的永久代,并且元空间的存储位置是本地内存。之前永久代的类的元数据存储在了元空间,永久代的静态变量(class static variables)以及运行时常量池(runtime constant pool)则跟 Java7 一样,转移到了堆中。
运作特点上:
- Metasace,无论-XX:MetaspaceSize和-XX:MaxMetaspaceSize两个参数如何设置,随着类加载越来越多不断扩容调整,直到MetaspaceSize触发FGC,上限是-XX:MaxMetaspaceSize,默认是几乎无穷大。
- Perm,我们通过配置-XX:PermSize以及-XX:MaxPermSize来控制这块内存的大小,jvm在启动的时候会根据-XX:PermSize初始化分配一块连续的内存块,这样的话,如果-XX:PermSize设置过大,就是一种赤果果的浪费,所以相对来说Metasace比Perm优秀的多~
官方给出的解释是:
- 1、移除永久代是为了融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,所以不需要配置永久代。
- 2、永久代内存经常不够用或发生内存溢出,爆出异常 java.lang.OutOfMemoryError: PermGen。这是因为在 JDK1.7 版本中,指定的 PermGen 区大小为 8M,由于 PermGen 中类的元数据信息在每次 FullGC 的时候都可能被收集,回收率都偏低,成绩很难令人满意;还有,为 PermGen 分配多大的空间很难确定,PermSize 的大小依赖于很多因素,比如,JVM 加载的 class 总数、常量池的大小和方法的大小等。 MetaspaceSize和MaxMetaspaceSize设置一样大;
最后的结论是:
- 1、错误的当然认为jstat -gcutil 里面的百分比就是元空间触发CMS FullGC的条件;
- 2、jdk环境变量的-XX:MetaspaceSize配置值才是触发FullGC的条件;
- 3、-XX:MetaspaceSize具体设置多大,建议系统稳定运行一段时间后通过jstat -gc pid确认且这个值相对大一些,对于大部分项目256m即可。