记一次java.lang.OutOfMemoryError: unable to create new native thread

       记录一次因为第三方工具使用不当引发的服务器内存被耗尽,导致Java服务无法创建新线程的OOM,当时太忙没有抽出时间来记录,现在只能凭借记忆和其他文章来还原当时问题的排查流程日后备用。

问题发现:

       这个问题是在开发新需求时,在测试环境被暴露出来的,测试反馈说所有的接口突然都调不通了。

解决流程:

1、接口报异常连上服务器tail日志,这个是当时日志报出的异常现场截图:(异常描述的清晰明了:无法创建新的本机线程)

2、通过对新提交的代码Review并没有发现可能出现问题的地方(后来发现确实是代码的问题)。

3、代码没发现问题的话,试着修改了Linux系统关于JVM的配置如:-Xss、-Xms、-Xmx等,然并卵。

        -Xss 128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆 栈大小为1M,以前每个线程堆栈大小为256K。在相同物理内存下,Xss越大,每个线程的大小就越大,占用的内存越多,能容纳的线程就越少;Xss越小,则递归的深度越小,容易出现栈溢出 java.lang.StackOverflowError。。但是操作系统对一 个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

4、几番周测,在整理思路后,决定首要任务就是如何重现该问题,于是编写测试程序,测试出操作系统最大能够创建的线程数:

import java.util.concurrent.CountDownLatch;

public class TestNativeOutOfMemoryError {

    public static void main(String[] args) {

        for (int i = 0;; i++) {
            System.out.println("i = " + i);
            new Thread(new HoldThread()).start();
        }
    }

}

class HoldThread extends Thread {
    CountDownLatch cdl = new CountDownLatch(1);

    public HoldThread() {
        this.setDaemon(true);
    }

    public void run() {
        try {
            cdl.await();
        } catch (InterruptedException e) {
        }
    }
}

运行后:

  i = 1002
  Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
      at java.lang.Thread.start0(Native Method)
      at java.lang.Thread.start(Thread.java:597)
      at TestNativeOutOfMemoryError.main(TestNativeOutOfMemoryError.java:20)

  问题重现,在反复运行几次后发现,生产系统最大只能创建1002多个线程。而我本地PC电脑都可以创建2500左右。

操作系统64位CentOS release 6.7 (Final),java version "1.8.0_101",64G内存。

  感觉原因快找到了,切换到运行账户使用命令:

  生产上所有程序都是在jenkins账户下运行,于是查看该账户下所有的线程数总和为908,也即是说,随时都可能会超过1002,导致内存溢出。查看看当前运行的线程数命令为:

[jenkins@localhost ~]$ ps -eLf | wc -l

  原因找到,操作系统对运行程序的账户有最大线程数限制。

[root@localhost ~]# cat /etc/security/limits.d/90-nproc.conf

 

打开后发现除了root,其他账户都限制在8096个。

  于是增加一条:jenkins     soft    nproc     20000

  为什么设置为20000,因为测试后发现,在运行到35000左右,系统就报内存溢出了,操作系统所有命令都不能使用,因此将程序最大线程数限制在20000。

修改后问题未解决(因为问题不是出现在OS上),但是操作系统可创建的线程数增加了很多。

总结:

一:修改JVM的-Xss参数,减小新创建线程的内存占用,相同的物理内存下可以创建更多的线程。

二:修改/etc/security/limits.d/90-nproc.conf中关于操作系统对用户最大线程数的限制

 

备注:

Apache的HttpClient据说很强大,这次的原因是HttpClient的资源释放和超时处理的配置不当,导致每次请求都会新建线程,并且新建的线程不会被回收,最终将服务器的内存耗尽。关于HttpClient的这个问题可以参考这篇文章

 

文章参考:

https://www.cnblogs.com/myshare/archive/2016/02/02/5177135.html

https://blog.csdn.net/penghaiping1001/article/details/73199300

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章