关于java.lang.OutOfMemoryError知多少(三)

一、java.lang.OutOfMemoryError之Unable to create new native thread

1、概述

java应用本质上是多线程的,也就意味同时可以做多个任务。即使在单处理器的机器上面,也可以通过切换时间窗口,不会因为同时执行多个任务导致其他任务被终止。
其实可以把这些看作是可以提交任务的员工。如果你只有一名工人,他或她只能在当时执行一项任务。但当你有12名员工时,他们可以同时完成你的多项任务。
JVM中的线程完成自己的工作也是需要一些空间的,当有足够多的线程却没有那么多的空间时就会像这样:


多线程.png

此时若是出现java.lang.OutOfMemoryError:Unable to create new native thread就意味着Java应用程序已达到其可以启动线程数量的极限了。

2、原因

当JVM向OS请求创建一个新线程时,而OS却无法创建新的native线程时就会抛出Unable to create new native thread错误。一台服务器可以创建的线程数依赖于物理配置和平台,建议运行下文中的示例代码来测试找出这些限制。总体上来说,抛出此错误会经过以下几个阶段:

  • 运行在JVM内的应用程序请求创建一个新的线程
  • JVM向OS请求创建一个新的native线程
  • OS尝试创建一个新的native线程,这时需要分配内存给新的线程
  • OS拒绝分配内存给线程,因为32位Java进程已经耗尽内存地址空间(2-4GB内存地址已被命中)或者OS的虚拟内存已经完全耗尽
  • Unable to create new native thread错误将被抛出
3、实例
while(true){
    new Thread(new Runnable(){
        public void run() {
            try {
                Thread.sleep(10000000);
            } catch(InterruptedException e) { }        
        }    
    }).start();
}

当代码运行时,很快达到OS的线程数限制,并抛出Unable to create new native thread错误

4、解决方案

常见的一般性处理方式:
1、限制了JVM在用户空间中产生的进程数量
2、在OS级别增加线程数限制来绕过这个错误
例如

[root@dev ~]# ulimit -a
core file size          (blocks, -c) 0
--- cut for brevity ---
max user processes              (-u) 1800

当应用程序需要创建大量的线程,并抛出此异常,表示程序已经出现了很严重的编程错误,此时不应该通过修改参数来解决这个问题,不管是OS级别的参数还是JVM启动参数。
更可取的办法是分析应用:
是否真的需要创建如此多的线程来完成任务?
是否可以使用线程池或者说线程池的数量是否合适?
是否可以更合理的拆分业务来实现?

二、java.lang.OutOfMemoryError之Out of swap space

1、概述

在Java应用程序启动过程中,可以通过- xmx和其他类似的启动参数限制指定的所需的内存。而当JVM所请求的总内存大于可用物理内存的情况下,操作系统开始将内容从内存转换为硬盘。


内存交换.png

若是此时抛出Out of swap space,则表示交换空间也将耗尽,并且由于缺少物理内存和交换空间,再次尝试分配内存也将失败。

2、原因

一般来说JVM会抛出Out of swap space错误,代表应用程序向JVM native heap请求分配内存失败并且native heap也即将耗尽时,错误消息中包含分配失败的大小(以字节为单位)和请求失败的原因。
java进程进行交换时,会导致延迟问题,即使现代的GC算法已经做得足够好,GC暂停的时间往往远超大多数应用程序不能容忍。将会触发操作系统级别的问题。

  • 操作系统配置的交换空间不足。
  • 系统上的另一个进程消耗所有内存资源
  • 本地内存泄漏导致应用程序失败(应用程序调用了native code连续分配内存,但却没有被释放)
3、解决方案

此类问题一般需要结合操作系统层面来处理,通用方式是增加交换空间。

swapoff -a 
dd if=/dev/zero of=swapfile bs=1024 count=655360
mkswap swapfile
swapon swapfile

Java GC会扫描内存中的数据,如果是对交换空间运行垃圾回收算法会使GC暂停的时间增加几个数量级,是需要慎重考虑使用上文增加交换空间的方法。
若是应用程序部署在JVM需要同其他进程激烈竞争获取资源的物理机上,建议将服务隔离到单独的虚拟机中。比如:

  • 升级机器以包含更多内存
  • 优化应用程序以减少其内存占用

三、java.lang.OutOfMemoryError之Requested array size exceeds VM limit

1、概述

一般来说java对应用程序所能分配数组最大大小是有限制的,只不过不同的平台限制有所不同,但通常在1到21亿个元素之间。


数组分配.png

当Requested array size exceeds VM limit错误出现时,意味着应用程序试图分配大于Java虚拟机可以支持的数组。

2、原因

JVM在为数组分配内存之前,会执行特定平台的检查:分配的数据结构是否在此平台是可寻址的。
不过这个错误一般少见的,主要是由于Java数组的索引是int类型。 Java中的最大正整数为2 ^ 31 - 1 = 2,147,483,647。 并且平台特定的限制可以非常接近这个数字,例如:我的环境上(64位macOS,运行Jdk1.8)可以初始化数组的长度高达2,147,483,645(Integer.MAX_VALUE-2)。若是在将数组的长度再增加1达到nteger.MAX_VALUE-1会出现的OutOfMemoryError,类似如下的信息:

Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit

不过OpenJDK 6的32位Linux上,分配大约11亿个元素的数组时,就会遇到Requested array size exceeded VM limit的错误。此类问题需要结合特定环境来分析。

3、实例
for (int i = 3; i >= 0; i--) {
    try {
        int[] arr = new int[Integer.MAX_VALUE-i];
        System.out.format("Successfully initialized an array with %,d elements.\n", Integer.MAX_VALUE-i);
    } catch (Throwable t) {
        t.printStackTrace();
    }
}

执行上述代码,则会出现如下熟悉的信息
java.lang.OutOfMemoryError: Java heap space
at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)
java.lang.OutOfMemoryError: Java heap space
at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)
此处出现的java.lang.OutOfMemoryError: Java heap space,是由于初始化2 ^ 31-1个元素的数组需要分配8G的内存空间,大于JVM使用的默认值。

4、解决方案

首先出现java.lang.OutOfMemoryError:Requested array size exceeds VM limit的情况一般如下:

  • 数组增长太大,最终大小在平台限制和Integer.MAX_INT之间
  • 有意分配大于2 ^ 31-1个元素的数组
    问题一方案:检查自己的代码,是否真的需要这么大的数组。也许可以减少数组的大小,或者将数组分成更小的数据块,然后分批处理数据
    问题二方案:java数组是由int索引的。在平台中使用标准数据结构时,数组不能超过2 ^ 31-1个元素。实际上会在编译时就会出错:error:integer number too large。

四、java.lang.OutOfMemoryError之Out of memory:Kill process or sacrifice child

1、概述

在描述该问题之前,先熟悉一点操作系统的知识:操作系统是建立在进程的概念之上,这些进程在内核中作业,其中有一个非常特殊的进程,称为“内存杀手(Out of memory killer)”。当内核检测到系统内存不足时,OOM killer被激活,检查当前谁占用内存最多然后将该进程杀掉。


进程.png

一般Out of memory:Kill process or sacrifice child错会在当可用虚拟虚拟内存(包括交换空间)消耗到让整个操作系统面临风险时,会被触发。在这种情况下,OOM Killer会选择“流氓进程”并杀死它。

2、原因

在linux操作系统中,内核允许进程获取足够多的内存,但实际上进程并没充分使用所分配的内存。这就跟现实生活中的宽带运营商类似,他们向所有消费者出售一个100M的带宽,100M已超过用户实际使用的带宽,一个10G的链路可以非常轻松的服务100个(10G/100M)用户,但实际上宽带运行商往往会把10G链路用于服务150人或者更多,以便让链路的利用率更高,毕竟空闲在那儿也没什么意义。
Linux内核采用的机制其实和上述例子的运营商类似,一般情况下是没有问题。但当大多数应用程序都消耗完自己的内存时,问题就会出现了,由于这些应用程序的内存需求加起来超出了物理内存(包括 swap)的容量,内核(OOM killer)必须杀掉一些进程才能腾出空间保障系统正常运行。

3、实例
public static void main(String[] args){
    List<int[]> l = new java.util.ArrayList();
    for (int i = 10000; i < 100000; i++) {
        try {
            l.add(new int[100000000]);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

执行上例 就会出现如下的错误信息【linux日志位于/var/log/kern.log 】
Jun 4 07:41:59 plumbr kernel: [70667120.897649] Out of memory: Kill process 29957 (java) score 366 or sacrifice child
Jun 4 07:41:59 plumbr kernel: [70667120.897701] Killed process 29957 (java) total-vm:2532680kB, anon-rss:1416508kB, file-rss:0kB

4、解决方案

一般处理方式调整交换文件和堆大小,来延迟Java heap space异常的出现,在上述测试用例中,使用-Xmx2g指定的2g堆:
swapoff -a
dd if=/dev/zero of=swapfile bs=1024 count=655360
mkswap swapfile
swapon swapfile
不过最有效也是最直接解决这个问题的方法就是升级内存。通过其他方式:

  • 调整OOM Killer配
  • 水平扩展应用,将内存的负载分摊到若干小实例上
    虽然增加交换空间的方式可以缓解Java heap space异常,但它不是我们解决方案选择之一。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,235评论 11 349
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,050评论 25 707
  • 今年九月刚刚升入大学的我满怀着期望与憧憬进入了大学校园,可是呢一走进大学一颗火热的从嗓子眼一下掉进了肚脐眼,破...
    云笙abc阅读 635评论 0 0
  • 春归晓无寒,月落水生烟。 东风绿阡陌,花开满深山。
    林夕敬阅读 281评论 18 4
  • 没有过艰苦爬行的岁月,就不足以感叹人生 说起来比较窝囊,毕业时没想过去一线城市奋斗,灰溜溜地跑回了省会城市。而毕业...
    深夜乘客阅读 225评论 0 0