美文网首页
关于java.lang.OutOfMemoryError知多少(

关于java.lang.OutOfMemoryError知多少(

作者: 神奇的考拉 | 来源:发表于2017-11-01 19:52 被阅读0次

    一、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异常,但它不是我们解决方案选择之一。

    相关文章

      网友评论

          本文标题:关于java.lang.OutOfMemoryError知多少(

          本文链接:https://www.haomeiwen.com/subject/fjtipxtx.html