`

解决 – java.lang.OutOfMemoryError: unable to create new native thread

    博客分类:
  • j2ee
 
阅读更多

一、认识问题:

首先我们通过下面这个  测试程序  来认识这个问题:
运行的环境  (有必要说明一下,不同环境会有不同的结果):32位 Windows XP,Sun JDK 1.6.0_18, eclipse 3.4,
测试程序:

01 import java.util.concurrent.CountDownLatch;
02  
03 public class TestNativeOutOfMemoryError {
04  
05      public static void main(String[] args) {
06  
07          for ( int i = 0 ;; i++) {
08              System.out.println( "i = " + i);
09              new Thread( new HoldThread()).start();
10          }
11      }
12  
13 }
14  
15 class HoldThread extends Thread {
16      CountDownLatch cdl = new CountDownLatch( 1 );
17  
18      public HoldThread() {
19          this .setDaemon( true );
20      }
21  
22      public void run() {
23          try {
24              cdl.await();
25          } catch (InterruptedException e) {
26          }
27      }
28 }

不指定任何JVM参数,eclipse中直接运行输出,看到了这位朋友了吧:
i = 5602

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)

二、分析问题:

这个异常问题本质原因是我们创建了太多的线程,而能创建的线程数是有限制的,导致了异常的发生。能创建的线程数的具体计算公式如下:  
(MaxProcessMemory – JVMMemory – ReservedOsMemory) / (ThreadStackSize) = Number of threads  
MaxProcessMemory 指的是一个进程的最大内存
JVMMemory         JVM内存
ReservedOsMemory  保留的操作系统内存
ThreadStackSize      线程栈的大小

在java语言里, 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而 是系统中剩下的内存(MaxProcessMemory – JVMMemory – ReservedOsMemory)。

结合上面例子我们来对公式说明一下:  
MaxProcessMemory 在32位的 windows下是 2G
JVMMemory   eclipse默认启动的程序内存是64M
ReservedOsMemory  一般是130M左右
ThreadStackSize 32位 JDK 1.6默认的stacksize 325K左右
公式如下:
(2*1024*1024-64*1024-130*1024)/325 = 5841 
公式计算所得5841,和实践5602基本一致(有偏差是因为ReservedOsMemory不能很精确)

由公式得出结论:你给JVM内存越多,那么你能创建的线程越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread。  

咦,有点背我们的常理,恩,让我们来验证一下,依旧使用上面的测试程序,加上下面的JVM参数,测试结果如下:  
ThreadStackSize      JVMMemory                    能创建的线程数
默认的325K             -Xms1024m -Xmx1024m    i = 2655
默认的325K               -Xms1224m -Xmx1224m    i = 2072
默认的325K             -Xms1324m -Xmx1324m    i = 1753
默认的325K             -Xms1424m -Xmx1424m    i = 1435
-Xss1024k             -Xms1424m -Xmx1424m    i = 452
 
完全和公式一致。

三、解决问题:  
1, 如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。
2, 如果程序确实需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数:
a, MaxProcessMemory 使用64位操作系统
b, JVMMemory   减少JVMMemory的分配
c, ThreadStackSize  减小单个线程的栈大小

//—————————————————————————–华丽的分割线——————————————————————————————//

星期一早上到了公司,据称产品环境抛出了最可爱的异常 —OutOfMemory, 它是这样来描述他自己的:

java.lang.OutOfMemoryError: unable to create new native thread

而且这位仁兄竟然还堂而皇之地同时出现在了 3 application 里面,所有应用全部遭殃。

那可爱的 OOM 是如何产生的呢?直接原因是创建的线程太多了,根本原因是某个地方的内存限制了。

搜罗了一下在网上找到了一个计算公式:

(MaxProcessMemory – JVMMemory – ReservedOsMemory) / (ThreadStackSize) = Number of threads  

MaxProcessMemory :进程最大的寻址空间,但我想这个值应该也不会超过虚拟内存和物理内存的总和吧。关于不同系统的进程可寻址的最大空间,可参考下面表格:

Maximum Address Space Per Process

Operating System

Maximum Address Space Per Process

Redhat Linux 32 bit

2 GB

Redhat Linux 64 bit

3 GB

Windows 98/2000/NT/Me/XP

2 GB

Solaris x86 (32 bit)

4 GB

Solaris 32 bit

4 GB

Solaris 64 bit

Terabytes

JVMMemory: Heap + PermGen

ReservedOSMemory Native heap JNI

便可推导出单个 JVM Instance可支持的最大线程数的估计值:

(MaxProcessMemory<固定值 > – Xms<初始化值,最小值 > – XX:PermSize<初始化值,最小值 > – 100m<估算值 >) / Xss = Number of threads<最大值 >

在本地 (32bit windows)试了试,可达的线程的最大值差不多就是这个数,它不受物理内存的限制,会利用虚拟内存,从任务管理器看到 memory已经是 5500 m左右了(开了两个 jvm),我机器的物理内存是 2g,也不知道这个准不准,后来还抛出了“ unable to create new native thread ”的兄弟“ Exception in thread "CompilerThread0" java.lang.OutOfMemoryError: requested 471336 bytes for Chunk::new. Out of swap space?“。

本地测完了后,就该轮到 dev环境了, linux2.6 64bit,双核, 8G(虚拟机),总的物理内存是 16g。在上面整了一下,创建到了 15000多个线程的时候挂掉了。此时其他 application也不能创建新的线程,而且 db也报错了,操作系统不能 fork新的线程了。这应该是操作系统的哪里限制了新线程的创建,

·          max thread linux2.6似乎是 32000

·          最大可用内存:物理内存 +虚拟内存

·          配置,在 linux可以限制可用资源的大小, show一下这些参数

core file size          (blocks, -c) 0

data seg size           (kbytes, -d) unlimited

file size               (blocks, -f) unlimited

pending signals                 (-i) 1024

max locked memory       (kbytes, -l) 32

max memory size         (kbytes, -m) unlimited

open files                      (-n) 65536

pipe size            (512 bytes, -p) 8

POSIX message queues     (bytes, -q) 819200

stack size              (kbytes, -s) 10240

cpu time               (seconds, -t) unlimited

max user processes              (-u) 16384

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

为了进一步确定在 linux上一个 jvm因为达到了最大寻址空间 OOM了,不会影响其他 jvm,我在 Linux做了进一步测试,一开始用 Sun文档中说的最大寻址空间 3G试了一下,发现根本不对,达到了 3G后还是非常 high地在创建新的线程。于是出动超级无敌变态的 JVM初始化配置。

oracle   27408 27017 12 13:45 ?        00:00:07 /home/oracle/ias1013/FWAPP/FWDev/jdk/bin/java -server -Xmx4096m -Xms4096m -XX:+HeapDumpOnOutOfMemoryError -XX:PermSize=4096m -XX:MaxPermSize=4096m -XX:HeapDumpPath=/home/oracle/ias1013/FWAPP/FWDev/j2ee/OC4J_OOMTest/workEnv/log -Xss100m

结果在 create 3379个线程后,“ unable to create new native thread ”出现了,这时其他 jvm都是可以 create新线程的。如果按照上面公式计算, linux 64bit 2.6kernel,它的最大寻址空间肯定超过了 300g,当然应该还没有达到可用内存的限制,因为其他 JVM还 create新线程。

我还怀疑是不是 oracle application server上的某个配置参数限制了总的线程数,影响了所有 application,但我们的产品环境一个 application就是一个单独的application server。

现在基本上可以确定是操作系统哪里设置错了,我想 System team的帅哥们应该把产品环境的某个参数配置错了,系统本身的影响肯定不会有了,因为产品环境上我们只 create 800左右个线程,就OOM了,那应该就是配置的问题了,怀疑的参数有下面四个

max user processes              (-u) 2048

virtual memory          (kbytes, -v) unlimited

max memory size         (kbytes, -m) unlimited

stack size              (kbytes, -s) 10240

最后发现只有 max user processes  virtual memory对总的线程数有影响,我把 max user processes降到 2048后,发现此时只能创建  2000左右个线程了 (Xms64m, Xss1m),进一步地把 virtual memory下调到 2048000K发现能创建的就更少了 1679 Xms64m, Xss1m),而它只会对当前shell起作用,而多个application server应该是不同的shell,所以他是打酱油的 。另外两个参数好像就是来做做俯卧撑的,操作系统 stack size是不应该会有什么影响,我们把它上调到 102400,还是可以创建 2000左右的线程数( max user processes), 因为 java有自己的线程模型,它的栈的大小是用 Xss来控制的。 Max memory size不知道是啥东东,照理说如果是最大内存应该不会只在旁边做俯卧撑,那这个参数到底是春哥还是曾哥,查了一下 man ulimit,有下面解释

              -a     All current limits are reported

              -c     The maximum size of core files created

              -d     The maximum size of a process data segment

              -f     The maximum size of files created by the shell

              -l     The maximum size that may be locked into memory

              -m     The maximum resident set size (has no effect on Linux)

              -n     The maximum number of open file descriptors (most systems do not allow this value to be set)

              -p     The pipe size in 512-byte blocks (this may not be set)

              -s     The maximum stack size

              -t     The maximum amount of cpu time in seconds

              -u     The maximum number of processes available to a single user

              -v     The maximum amount of virtual memory available to the shell

Has no effect on Linux”就足以证明它确实只是来做做俯卧撑的。最后查出只有“ max user processes”会对所有 application能创建总的线程数有限制。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics