深入理解OutOfMemoryError

Published: 24 Feb 2014 Category: Java

深入理解OutOfMemoryError

当堆栈跟踪信息里面出现OutOfMemoryError的时候,你应该很清楚发生了什么。应用程序由于没有足够的内存空间所以挂了。知道这个一般就也够了,但是对于构建和维护应用程序的人来说,要想查清楚为什么报错的话,这里倒可以再多分享一点经验。

在这篇文章里面,我们主要介绍一下不同的OutOfMemoryError具体是什么含义。我们先从最简单的例子开始,后面会有一些更有意思的话题。

java.lang.OutOfMemoryError: Java heap space java.lang.OutOfMemoryError: PermGen space java.lang.OutOfMemoryError: GC overhead limit exceeded java.lang.OutOfMemoryError: unable to create new native thread java.lang.OutOfMemoryError: nativeGetNewTLA java.lang.OutOfMemoryError: Requested array size exceeds VM limit java.lang.OutOfMemoryError: request bytes for . Out of swap space? java.lang.OutOfMemoryError: (Native method)

java.lang.OutOfMemoryError: Java heap space。
我们先从大家都见过却并不喜闻乐见的开始。这是Java虚拟机在抱怨堆里面已经没有更多的空间了。你正准备创建一个新的对象,但是这个要创建的对象需要的内存已经超过了虚拟机所剩的了。虚拟机会尝试通过full GC来回收内存,如果不行的话,就会抛出这个信息。解决这个问题最快的方法就是通过-Xmx参数来增加堆的大小。注意,本文中的建议需要有保留的接受。很多时候可能你就不了了之了,却不去找出问题的根源。

下一个问题也很常见。我相信很多人在重新部署的时候 都遇见过

java.lang.OutOfMemoryError: PermGen space
。这个和第一个现象差不多,不过这里准备分配内存的空间是持久代。同样的,你的空间已经不够了,所以虚拟机善意的提醒了你一下。如果你增加了-XX:MaxPermSize这个参数的值的话,这个问题通常就解决了。

第三个,java.lang.OutOfMemoryError: GC overhead limit exceeded,这个问题有点特殊。这里没有提示说堆还是持久代有问题,虚拟机只是告诉你你的程序花在垃圾回收上的时间太多了,却没有什么见效。默认的话,如果你98%的时间都花在GC上并且回收了才不到2%的空间的话,虚拟机才会抛这个异常。这是一个快速失败的安全保障的很好的实践。一般来说禁用它也没有太大用处,如果需要的话你可以把-XX:-UseGCOverheadLimit加到启动脚本里。

在我们使用Plumbr解决的问题中,前三个例子覆盖了98%以上的场景。因此下面这几个你很有可能不太清楚。

java.lang.OutOfMemoryError: unable to create new native thread,如果虚拟机正在请求操作系统创建一个本地线程,而操作系统无法创建的时候,你会收到这个报错信息。这个限制的大小跟平台相关,如果你对这个限制的大小好奇的话,可以用下面这小段代码做下试验。在我的64位的Mac OSX上使用最新的JDK7,当创建的线程到2032时的就会报错。

while(true){
    new Thread(new Runnable(){
        public void run() {
            try {
                Thread.sleep(10000000);
            } catch(InterruptedException e) { }        
        }   
    }).start();
}

java.lang.OutOfMemoryError: nativeGetNewTLA指当虚拟机不能分配新的线程本地空间(Thread Local Area)的时候错误信息。这个异常只有在jRockit虚拟机时才会碰到。线程本地空间是多线程程序里面为了更有效的进行内存分配而建立的缓存。每一个线程都有一份自己的缓存,当这个线程要创建对象的时候,就在这上面分配。如果你有很多线程同时并发,又要创建大量的对象,可能会出现这个问题,这种情况下你可以调整一下-XXtlaSize这个参数。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit
,当你正准备创建一个超过虚拟机允许的大小的数组时,这条错误就会出现在你眼前。在我的64位Mac系统上的最新的JDK7,我发现如果数组的长度是Integer.MAXINT-2的时候,是正常的,但只要再增加一个,也就是Integer.MAXINT-1,就成为那最后一根稻草了。在老的32位机器上,由于堆比较小,限制数组的大小是有好处的。不过在现代的64位机器上感觉有点多余。

java.lang.OutOfMemoryError: request bytes for . Out of swap space?
这个错误是当虚拟机向本地操作系统申请内存失败时抛出的。这和你用完了堆或者持久化中的内存的情况有些不同。这个错误通常是在你的程序已经逼近平台限制的时候产生的。这个信息告诉你的是你可能已经用光了物理内存以及虚拟内存了。由于虚拟内存通常是用磁盘作为交换分区,因此你最先想到的解决方法可能是先增加交换分区的大小。不过我从没见过一个程序在频繁进行内存交换还能正常运行的,所以这个方法可能不会起到什么作用。

java.lang.OutOfMemoryError: (Native method)
,现在是时候找你开发C语言的小伙伴请求帮助了。因为现在你看到的错误信息是来自本地代码的,相对于刚才的出错信息,这次异常是在JNI或者 本地方法中检测到的,而不是在虚拟机执行的代码中。

本文中的建议都应该有选择性的参考。通常你可能就这么不了了之了,而忽略了问题的本质。如果你想知道这是不是产生了内存泄露的话,可以下载我这个Plumbr来进行测试

原文链接