现网gc问题定位三板斧
国庆假期临近,组里的小伙伴们都开开心心请假回家了,然后cgi很应景的出现了多台机器的频繁full gc,所以只能上阵用力撸一把了。
第一板斧:看gc日志。
首先登上机器,进入日志目录/usr/local/services/javacgi_qqke_web-2.0/log
javacgi_qqke_web这个工程是比较有节操的了,配置了输出gc日志。直接grep一下FULL GC,看下是不是和告警匹配。
用力敲下面的命令
grep -rn "Full GC" cgi_ke_web.gclog
观察下FULL GC的时间,明显太频繁了,而且老年代每次回收完都没什么变化,可以初步判断是内存泄漏了,有大量的内存回收不了
ps,如果遇到没有节操的工程,没有输出gc日志,不用怕,现场实时观察就好
用力敲下下面的命令,21332是pid,1000毫秒输出一次,输出10次。
/usr/local/services/jdk7_32-1.0/bin/jstat -gcutil 21332 1000 10
第二板斧:jmap看堆内存情况
/usr/local/services/jdk7_32-1.0/bin/jmap -heap 21332
通过jmap工具,可以清楚看到,老年代耗尽了,但是永久带也不够接受老年代的数据,所以无论怎么回收,老年代不减少,导致了频繁 FULL GC
第三板斧:MAT
首先先用jmap工具把堆dump下来。
/usr/local/services/jdk7_32-1.0/bin/jmap -dump:live,format=b,file=dump.hprof 21332
把文件下载到本地机器,MAT是eclipse出品,windows使用正常,mac能不能用看造化
别忘了修改这个文件,调整MAT内存大小,不然装不下服务器的堆文件的
导入成功后,直接点内存泄漏分析
分析的结果告诉我们Sppclient中疑似内存泄漏。
仔细看一下内存分析,内存主要用在两个和后端spp-qun服务连接的qcon上面,每个qcon占用了300M+的内存,而且sppclient是全局单例,里面的数据都是不会回收的,这就是造成内存泄漏的主要来源。
看下这个队列里面的元素,有131672个。。
再去看下代码
public boolean write(Object msg, ChannelFutureListener connectionListener, ChannelFutureListener writeListener) {
if (channels.size() < maxChannels) {
return doConnectAndWrite(msg, connectionListener, writeListener);
} else {
Object[] ca = channels.toArray();
int size = ca.length;
nextChannel = (nextChannel + 1) % size;
Channel c = (Channel) ca[nextChannel];
if (c.isConnected()) {
c.write(msg).addListener(writeListener);
} else {
ChannelFuture connFuture = ch2connFuture.get(c);
if (connFuture != null) {
connFuture.addListener(connectionListener);
} else {
log.error("no connection future!!! channel={}", c);
}
}
return true;
}
}
private boolean doConnectAndWrite(final Object msg, ChannelFutureListener connectionListener, final ChannelFutureListener writeListener) {
//新建tcp连接
MonitorUtils.monitor(3222607);
ChannelFuture future = bootstrap.connect(addr);
Channel channel = future.getChannel();
future.addListener(connectionListener);
ch2connFuture.put(channel, future);
//这里注意,这个channel可能还未连接成功
channels.add(channel);
return true;
}
看完代码,更怀疑人生了,理论上ch2connFuture 和 channels 数量应该是一致的,而且这个队列设置了上限65535,为啥现网会超出这么多。。
这时我们再回去看下MAT中的数据,channels只有17个。。
为啥?!!直到看完了add的实现,才终于恍然大悟
@Override
public boolean add(Channel channel) {
ConcurrentMap<Integer, Channel> map =
channel instanceof ServerChannel? serverChannels : nonServerChannels;
boolean added = map.putIfAbsent(channel.getId(), channel) == null;
if (added) {
channel.getCloseFuture().addListener(remover);
}
return added;
}
关键就在于add的操作判断了channel里面是否已经有了即将添加的channel,而这个判断条件是对比channel的id,那么问题来了,这里的channel其实是一个异步初始化的future类型,在执行add操作的时候,并不能保证拿到id,所以这里会出现大量id为0的channel。。而只要和后端建立连接的时候出现网络问题,这里的bug就会触发,就会导致内存泄漏。
总结下,这次的full gc是因为网络抖动导致的spp-qun调用的框架bug在现网出现,最快的解决方法就是重启服务。。。。
小事重启果然是在哪里都适用的操作啊。。