[java] [error] java.lang.OutOfMemoryError: unable to create new native thread

前言

最近公司的服务器出现了oom的报错,经过一番排查,终于找到了原因。写下这篇博客是为了记录下查找的过程,也是为了帮助那些跟我门遇到的情况相同的人可以更快的寻找到答案。

 

环境

系统:linux(centos 7)

平台:java

 

介绍

程序结构介绍

《[java] [error] java.lang.OutOfMemoryError: unable to create new native thread》

上图为报错程序的结构图,从图中可以看出有一个主的JVM运行主要的java程序,当有请求到达时,对于每个请求都会启动一个JVM去处理请求。

 

报错介绍

 当上面所述的程序接受到的请求超过一定数量时,就会报错 java.lang.OutOfMemoryError: unable to create new native thread 

 

分析步骤

1.通过上面的报错信息可以得到以下结论:(ps:关于内存溢出这块的知识可以查看https://www.cnblogs.com/lin-xuan/p/5271354.html)

  • 该报错是内存溢出导致的
  • 内存溢出的地方在java虚拟机栈或者本地方法栈

2.通过进一步的百度我们找到了以下的解答:

  • 系统内存耗尽,无法为新线程分配内存
  •  创建线程数超过了操作系统的限制

 3.通过 top 命令查看在程序运行时内存的使用情况,发现当报错时内存的使用为50%,因此排除了内存耗尽的情况

4.通过 ulimit -u 命令查看系统允许用户最大创建线程数,发现用户最大可以创建1024个线程

《[java] [error] java.lang.OutOfMemoryError: unable to create new native thread》

5.通过 ulimit -u 4096 命令修改用户最大线程数,再次使用 ulimit -u 查看是否修改成功

《[java] [error] java.lang.OutOfMemoryError: unable to create new native thread》

6.当我们把用户最大线程数修改为4096时,我们发现可创建的JVM并没有增加多少,因此我们决定模拟环境,来排查具体的原因

7.创建文件SubJVM.java,并且输入以下内容:

import java.util.concurrent.TimeUnit;
public class SubJVM {
        public static void main(String...args) throws InterruptedException{
                TimeUnit.SECONDS.sleep(30);
        }
}

 

8.创建文件MainJVM.java,并且输入以下内容:

 

public class MainJVM {
        public static void main(String...args) {
                for(int i = 0; i < 10000; i++) {
                        System.out.println("Thread num:" + i);
                        Thread thread = new Thread(new Runnable() {
                                @Override
                                public void run() {
                                        try {
                                                Process ps = Runtime.getRuntime().exec("java SubJVM");
                                        } catch (Exception e) {
                                                e.printStackTrace();
                                        }
                                }
                        });
                        thread.start();
                }
        }
}

 

9.分别将 ulimit -u 设置为1024、4096、10000,然后会的到以下结果:

ulimit -uJVM数
102482
4096298
10000712

10.这时我们猜想每个JVM中会有若干个线程,统计如下:

1024/82=12
4096/298=13
10000/712=14

 

11.为了解释我们心中的猜想,我们使用了以下命令查看每个JVM的线程数(请自行百度查看该命令的使用方式):

jstack pid

 

12.该命令的输出内容如下:

2018-04-08 01:26:43
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.80-b11 mixed mode):

"Attach Listener" daemon prio=10 tid=0x00007f90c0001000 nid=0x2f3c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" daemon prio=10 tid=0x00007f90f4097800 nid=0x2d85 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=10 tid=0x00007f90f4095800 nid=0x2d82 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=10 tid=0x00007f90f4092800 nid=0x2d80 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x00007f90f4090000 nid=0x2d6b runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=10 tid=0x00007f90f406f000 nid=0x2d3b in Object.wait() [0x00007f90ed04e000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d7404858> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    - locked <0x00000000d7404858> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" daemon prio=10 tid=0x00007f90f406d000 nid=0x2d37 in Object.wait() [0x00007f90f81b2000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d7404470> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:503)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
    - locked <0x00000000d7404470> (a java.lang.ref.Reference$Lock)

"main" prio=10 tid=0x00007f90f4009000 nid=0x2cf0 waiting on condition [0x00007f90fd824000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at Sleep.main(Sleep.java:4)

"VM Thread" prio=10 tid=0x00007f90f4068800 nid=0x2d28 runnable 

"GC task thread#0 (ParallelGC)" prio=10 tid=0x00007f90f401e800 nid=0x2cf1 runnable 

"GC task thread#1 (ParallelGC)" prio=10 tid=0x00007f90f4020800 nid=0x2d08 runnable 

"GC task thread#2 (ParallelGC)" prio=10 tid=0x00007f90f4022800 nid=0x2d13 runnable 

"GC task thread#3 (ParallelGC)" prio=10 tid=0x00007f90f4024800 nid=0x2d17 runnable 

"VM Periodic Task Thread" prio=10 tid=0x00007f90f40a2800 nid=0x2d9d waiting on condition 

JNI global references: 107

 

13.统计以上信息可知,一个JVM中大概有14个线程,这也解释了为什么增加了 ulimit -u 的值,JVM的数量还是没有达到预期的值。

解决

1.切换到root用户

2.输入以下命令

vim /etc/security/limits.d/20-nproc.conf

 

3.在文件中加入以下内容:

*          soft    nproc     4096
root       soft    nproc     unlimited

 

点赞