经常在线排查故障
前段时间发现上线的一个dubbo服务Full GC比较频繁,大约每两天就会执行一次Full GC。
进行 Full GC 的原因
我们知道Full GC的触发条件一般有以下几种:
程序执行.gc() //建议jvm执行,但不一定执行jmap -histo:live pid命令 //这在执行minor gc时会立即触发一系列检查
执行Minor GC的时候,JVM会检查老年代中最大连续可用空间是否大于了当前新生代所有对象的总大小。
如果大于,则直接执行Minor GC(这个时候执行是没有风险的)。
如果小于了,JVM会检查是否开启了空间分配担保机制,如果没有开启则直接改为执行Full GC。
如果开启了,则JVM会检查老年代中最大连续可用空间是否大于了历次晋升到老年代中的平均大小,如果小于则执行改为执行Full GC。
如果大于则会执行Minor GC,如果Minor GC执行失败则会执行Full GC
使用大对象//大对象会直接进入老年代。 程序中,对象的引用已被持有很长时间//对象年龄达到指定阈值,也将进入老年龄
就我们的情况来说,可以初步排除1、2两种情况,而4、5两种情况可能性最大。 为了进一步排查原因,我们在线开启了-XX:+。
注意:
JVM在执行dump操作的时候是会发生stop the word事件的,也就是说此时所有的用户线程都会暂停运行。
为了在此期间也能对外正常提供服务,建议采用分布式部署,并采用合适的负载均衡算法
JVM参数设置:
线上dubbo服务是分布式部署,在其中一台机器上启用-XX:。 总体JVM参数如下:
-Xmx2g
-XX:+HeapDumpBeforeFullGC
-XX:HeapDumpPath=.
-Xloggc:gc.log
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=100m
-XX:HeapDumponOutOfMemoryError
转储文件分析
转储的文件大小约为1.8g。 我检查了一下,发现char[]类型的数据占用了41%的内存,还有一个com..druid.stat。 类型数据占据了内存的35%,这意味着整个堆几乎都充满了这两种类型的数据。 如下所示:
查看char[]类型的数据,发现几乎都是SQL语句。
接下来看一下char[]的引用:
找到了JdbcSqlStat类,在代码中查看这个类的代码,关键代码如下:
构造函数只有这一个
public JdbcSqlStat(String sql){
this.sql = sql;
this.id = DruidDriver.createSqlStatId();
}
查看这个函数的调用情况,找到com.alibaba.druid.stat.JdbcDataSourceStat#createSqlStat方法:
public JdbcSqlStat createSqlStat(String sql) {
lock.writeLock().lock();
try {
JdbcSqlStat sqlStat = sqlStatMap.get(sql);
if (sqlStat == null) {
sqlStat = new JdbcSqlStat(sql);
sqlStat.setDbType(this.dbType);
sqlStat.setName(this.name);
sqlStatMap.put(sql, sqlStat);
}
return sqlStat;
} finally {
lock.writeLock().unlock();
}
}
这里用了一个map来存放所有的sql语句。
其实到这里我们就知道是什么原因导致了这个问题了,因为我们使用的数据源是阿里巴巴的druid,它提供了SQL语句监控功能,而且我们也开启了这个功能。只要在配置文件中关闭这个功能就应该可以消除这个问题,而且确实是这样。 关闭该功能后,线上至今尚未触发。
其他
如果使用mat工具查看,建议勾选“Keep”,否则mat会删除堆中无法访问的对象,这样我们的分析可能会变得毫无意义。 如下图: --> . 另外对ool的支持也不是很好。 如果需要oql,建议使用mat。