记一次内存溢出的调试经历,线程无限创建,直到报OOM

问题

一个java服务运行差不多一天就会崩溃,报java.lang.OutOfMemoryError: unable to create new native thread

分析

可能原因1:服务器设置无法满足服务创建的线程数量 可能原因2:jvm配置无法满足创建的线程数量 可能原因3:代码问题,有死循环,线程在无限创建

解决

1.查看服务器linux用户的最大进程数,挺大的跟这个应该没关系

[root@localhost]# ulimit -u
126985

2.调节jvm启动时设置的每个线程的堆栈大小参数 Xss,原来是默认值1m,可以改成Xss256k,先看下一步; 3.其实分析下来大概率代码是有问题了,应该直接从这一步切入的,前面两步算是一种拓展学习了 先用“ps -ef|grep java”查看服务的PID(本文出问题的服务PID是134859,后文也有用到) 然后查看服务的进程数命令“ps hH p PID | wc -l”,显示304个进程,看似很正常

[root@localhost]# ps hH p 134859 | wc -l
304

过一会再看,没多长时间就多了两百多

[root@localhost]# ps hH p 134859 | wc -l
568

很明显这不正常,有什么代码在循环创建线程 jstack命令可以用来查看Java线程的调用堆栈的,可以用来分析线程问题,打印服务的线程日志到dump.txt文件

[root@localhost]# jstack -l 134859 >> dump.txt

补充说明:可以使用top -Hp PID 命令查看PID进程中的线程占用信息,然后根据查看到的占用高的线程PID换算成16进制,再从jstack导出的线程堆栈信息中查找这个16进制PID的报错信息

然后查看这个文件,发现大量类似于下面的线程TIMED_WAITING的记录

"threadPoolTaskExecutor-11" #54 prio=5 os_prio=0 tid=0x00007fc760036800 nid=0xbec3 waiting on condition [0x00007fc81fffe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at cn.doeat.blog.test.thr.PostStatusThr.run(PostStatusThr.java:97)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - <0x00000005d20dc8e0> (a java.util.concurrent.ThreadPoolExecutor$Worker)

"threadPoolTaskExecutor-10" #53 prio=5 os_prio=0 tid=0x00007fc760034800 nid=0xbec2 waiting on condition [0x00007fc824191000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at cn.doeat.blog.test.thr.PostStatusThr.run(PostStatusThr.java:97)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - <0x00000005d20dbf68> (a java.util.concurrent.ThreadPoolExecutor$Worker)
······
······
······

日志中有明确的出问题代码的位置,PostStatusThr.java文件第97行

at cn.doeat.blog.test.thr.PostStatusThr.run(PostStatusThr.java:97)

去看代码,是个线程的run方法,97行只是个break,看代码逻辑没有什么错误,就不贴出代码了

public class PostStatusThr implements Runnable {

    private boolean endFlag = true;
    private long sleepTime;
    private int maxMessageCount = 10;

    public PostStatusThr(boolean endFlag, long sleepTime,int maxMessageCount) {
        this.endFlag = endFlag;
        this.sleepTime = sleepTime;
        this.maxMessageCount = maxMessageCount;
    }

    @Override
    public void run() {
            ···
            ···
            ···
    }
}

PostStatusThr中有个有参的构造方法,那就看谁调用了这个类,发现只有PostStatusTask类调用了PostStatusThr的有参构造方法,此处也没有什么问题,只是运行创建线程池

@Component
public class PostStatusTask {
    @Autowired
    private ThreadPoolTaskExecutor executor;

    public void run() {
        try {
            int i = 0;
            while (i < 2) {
                executor.execute(new PostSensorStatusThr(true, 5000,10));
                i++;
            }

        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }
}

那再看看哪里调用了PostStatusTask类,寻根溯源找到了一处代码,这里相当于每隔3秒初始化一次线程池····,代码原本意图是项目启动时初始化调用创建线程池

@Component
public class InitializationTask {
    @Scheduled(fixedDelay =3000)
    public static synchronized void pollingLogs() {
        ServiceUtil.getPostStatusTask().run();
    }
}

修改代码使符合预期效果,springboot项目实现ApplicationRunner接口会在项目启动时运行实现类中的run方法

@Component
public class InitializationTask implements ApplicationRunner {
    public static synchronized void pollingLogs() {
        ServiceUtil.getPostStatusTask().run();
    }

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        pollingLogs();
    }
}