国庆假期临近,组里的小伙伴们都开开心心请假回家了,然后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 G现网gc问题定位三板斧-唯嘉利亚云安全C的时间,明显太频繁了,而且老年代每次回收完都没什么变化,可以初步判断是内存泄漏了,有大量的内存回收不了

ps,如果遇到没有节操的工程,没有输出gc日志,不用怕,现场实时观察就好

用力敲下下面的命令,21332是pid,1000毫秒输出一次,输出10次。

/usr/local/services/jdk7_32-1.0/bin/jstat -gcutil 21332 1000 10现网gc问题定位三板斧-唯嘉利亚云安全

第二板斧:jmap看堆内存情况

/usr/local/services/jdk7_32-1.0/bin/jmap -heap 21332现网gc问题定位三板斧-唯嘉利亚云安全

通过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能不能用看造化现网gc问题定位三板斧-唯嘉利亚云安全

别忘了修改这个文件,调整MAT内存大小,不然装不下服务器的堆文件的

导入成功后,直接点内存泄漏分析

现网gc问题定位三板斧-唯嘉利亚云安全现网gc问题定位三板斧-唯嘉利亚云安全

分析的结果告诉我们Sppclient中疑似内存泄漏。

现网gc问题定位三板斧-唯嘉利亚云安全仔细看一下内存分析,内存主要用在两个和后端spp-qun服务连接的qcon上面,每个qcon占用了300M+的内存,而且sppclient是全局单例,里面的数据都是不会回收的,这就是造成内存泄漏的主要来源。

现网gc问题定位三板斧-唯嘉利亚云安全看下这个队列里面的元素,有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;
}现网gc问题定位三板斧-唯嘉利亚云安全

看完代码,更怀疑人生了,理论上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在现网出现,最快的解决方法就是重启服务。。。。

现网gc问题定位三板斧-唯嘉利亚云安全

小事重启果然是在哪里都适用的操作啊。。