Spark - ERROR Executor: java.lang.OutOfMemoryError: unable to create new native thread

如果你的Spark程序在执行过程中报出如下类似的错误:

ERROR Executor: Exception in task xxx in stage xxx
java.lang.OutOfMemoryError: unable to create new native thread

那么可能性非常大的原因是你当前通过spark-submit或spark-sql启动的程序中开启了过多的进程,以至超过了操作系统对当前用户所允许打开的进程数的上限。确定这个问题的方法是这样的:

首先,通过命令:

ulimit -u
  • 1

来查看一下系统允许的当前用户能开启的进程数,默认值是1024。

然后,我们通过如下这样一条命令来持续追踪当前用户开启的进程数

while true;do ps -u your-user-name  -L | wc -l;sleep 1;done
  • 1

接下就可以启动的我们Spark程序然后观察进程数量的变化了,如果发现进程数量持续上涨,并且在超过了进程上限之后抛出了OOM异常,那么就可以直接判定是用户的所能开启的进程数量收受限了。

那么什么情况会导致spark在执行过程中开启大量的进程呢?一个可能的原因是由于数据的“无序”性,导致shuffle过程开销过大。


从JVM层面去解决

减小thread stack的大小

JVM默认thread stack的大小为1024,这样当线程多时导致Native virtual memory被耗尽,实际上当thread stack的大小为128K 或 256K时是足够的,所以我们如果明确指定thread stack为128K 或 256K即可,具体使用-Xss,例如在JVM启动的JVM_OPT中添加如下配置

[java]  view plain  copy
  1. -Xss128k  

减小heap或permgen初始分配的大小

如果JVM启动的JVM_OPT中有如下配置

[java]  view plain  copy
  1. -Xms1303m -Xmx1303m -XX:PermSize=256m -XX:MaxPermSize=256m  
我们可以删除或减小初始化最小值的配置,如下

[java]  view plain  copy
  1. -Xms256m -Xmx1303m -XX:PermSize=64m -XX:MaxPermSize=256m  

[java]  view plain  copy
  1. -Xmx1303m -XX:MaxPermSize=256m  

升级JVM到最新的版本

最新版本的JVM一般在内存优化方面做的更好,升级JVM到最新的版本可能会缓解测问题

从操作系统层面去解决

使用64位操作系统

如果使用32位操作系统遇到unable to create new native thread,建议使用64位操作系统

增大OS对线程的限制

在Linux操作系统设定nofile和nproc,具体编辑/etc/security/limits.conf添加如下:

[html]  view plain  copy
  1. soft    nofile          2048  
  2. hard    nofile          8192  

[html]  view plain  copy
  1. soft    nproc           2048  
  2. hard    nproc           8192  

如果使用 Red Hat Enterprise Linux 6,编辑/etc/security/limits.d/90-nproc.conf,添加如下配置:

[html]  view plain  copy
  1. # cat /etc/security/limits.d/90-nproc.conf  
  2. *          soft    nproc     1024  
  3. root       soft    nproc     unlimited  
  4.   
  5. user       -       nproc     2048  

首先我们通过下面这个 测试程序 来认识这个问题:

运行的环境 (有必要说明一下,不同环境会有不同的结果):32位 Windows XP,Sun JDK 1.6.0_18, eclipse 3.4,
测试程序:

Java代码   
  1. import java.util.concurrent.CountDownLatch;   
  2.   
  3. public class TestNativeOutOfMemoryError {   
  4.   
  5.     public static void main(String[] args) {   
  6.   
  7.         for (int i = 0;; i++) {   
  8.             System.out.println("i = " + i);   
  9.             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. }  
[java]  view plain  copy
  1. import java.util.concurrent.CountDownLatch;  
  2.   
  3. public class TestNativeOutOfMemoryError {  
  4.   
  5.     public static void main(String[] args) {  
  6.   
  7.         for (int i = 0;; i++) {  
  8.             System.out.println("i = " + i);  
  9.             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  减小单个线程的栈大小


你可能感兴趣的:(spark)