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