复盘:Java Runnable关闭逻辑不完善导致的内存泄漏

前言

配置在边缘节点的端口转发、数据包修改服务软件,发生 周期性 的服务中断。周期介于5~10天之间,尤易在周末发生。推测与流量有关。

中断时报错: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

内存占用出现逐渐增加的趋势。
陈年老图

应急处理

先发维护通知忽悠用户,然后趁机定期进行重启,清理内存。

肇事代码

//启动本地监听端口
ServerSocket serverSocket = new ServerSocket(localPort);
log.info("localPort="+localPort + ";remoteIp=" + remoteIp +
    ";remotePort="+remotePort+";启动本地监听端口" + localPort + "成功!");
while(true){
    Socket clientSocket = null;
    Socket remoteServerSocket = null;
    try {
        //获取客户端连接
        clientSocket = serverSocket.accept();
        //建立远程连接
        remoteServerSocket = new Socket(remoteIp ,remotePort);
        String clientIP = clientSocket.getLocalAddress().toString();
        clientIP = clientIP.substring(clientIP.lastIndexOf("/")+1);
        log.info("客户端IP:"+clientIP);
        //启动数据转换接口
        (new TransPortData(clientSocket ,remoteServerSocket ,"1")).start();
        (new TransPortData(remoteServerSocket ,clientSocket,"2")).start();
    } catch (Exception ex) {
        log.info("",ex);
    }
    //建立远程连接
}

原因分析

首先看Heap堆栈。
VisualVM截图

从图中可以看出存在大量的byte[],而上面代码中启动数据转换接口的同时,正是使用了一个byte[]变量储存TCP数据包。

InputStream in = getDataSocket.getInputStream() ;
//读入数据
byte[] data = new byte[2048];
int readlen = in.read(data);

为了进一步确认这个错误,在启动指令中加入分析功能,反馈如下:

nid达到百万级别,可以认为确实是线程清理不干净导致的。

代码优化

加入 while 1 退出机制

//如果没有数据,则暂停,超时销毁资源
if(readlen<=0){
    TimeoutStamp++;
    if(TimeoutStamp>=300){
        break;
    }
    Thread.sleep(100);
    continue;
}

并在finally中关闭数据包传输器

finally{
    //关闭socket
    try {
        if(putDataSocket != null){
            putDataSocket.close();
        }
    } catch (Exception exx) {
    }
    try {
        if(getDataSocket != null){
            getDataSocket.close();
        }
    } catch (Exception exx) {
    }
}

实测内存占用稳定,不再发生内存泄漏。

无标签