`
deepinmind
  • 浏览: 451581 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41632
社区版块
存档分类
最新评论

一次OutOfMemoryError异常的分析

阅读更多

为什么我给JVM分配的堆已经足够大了,但在给一个数据结构分配内存的时候却抛出了OutOfMemoryError异常?这是我最近面临的一个问题。


看了下开发人员这段代码到底是干什么的并且再三确认了通过-Xmx参数给JVM设置的堆大小之后,看样子问题确实是有点诡异了。


半小时后我们终于知道是怎么回事并解决了这个疑案。不过这个问题的确在一开始并不是那么明显,所以我想如果我把底层的问题描述得清楚点的话,或者以后能替大家节省掉一天的排查问题的时间。


通常来说,想弄清楚一个问题的最好方式就是通过一个实例来进行说明。这里我创建一个小的测试用例:


package eu.plumbr.demo;
class ArraySize {
public static void main(String... args) {
int[] array = new int[1024*1024*1024];
}
}
 




代码很简单——它要做的就是分配一个1G大小的数组。现在你想下,Java的int类型需要4个字节,那如果用个6G大小的堆来运行这个程序应该就没问题了。毕竟10亿个整型数值也就占用4G内存而已。那为什么我执行这段代码的时候会出现下面的结果?

My Precious:bin me$ java –Xms6g –Xmx6g eu.plumbr.demo.ArraySize
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at eu.plumbr.demo.ArraySize.main(ArraySize.java:6)
 




在增加堆的大小之前(事实上,如果使用-Xmx7g来运行上述程序就没问题了),我们先来看下为什么出现的不是我们预期的结果。


首先,int类型在Java的确是占用4个字节。因此这并不是我们的JVM在晚上突然发疯了。而且我还可以很确定地告诉你,我的计算也没有问题——1024*1024*1024个整型的确是需要4,294,967,296字节,也就是4G内存。


想知道发生了什么,我们先使用–XX:+PrintGCDetails参数把GC的日志打开之后再运行一遍这个程序看看:


My Precious:bin me$ java –Xms6g -Xmx6g -XX:+PrintGCDetails eu.plumbr.demo.ArraySize

-- cut for brevity --

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at eu.plumbr.demo.ArraySize.main(ArraySize.java:6)

Heap
 PSYoungGen      total 1835008K, used 125829K [0x0000000780000000, 0x0000000800000000, 0x0000000800000000)
  eden space 1572864K, 8% used [0x0000000780000000,0x0000000787ae15a8,0x00000007e0000000)
  from space 262144K, 0% used [0x00000007e0000000,0x00000007e0000000,0x00000007f0000000)
  to   space 262144K, 0% used [0x00000007f0000000,0x00000007f0000000,0x0000000800000000)
 ParOldGen       total 4194304K, used 229K [0x0000000680000000, 0x0000000780000000, 0x0000000780000000)
  object space 4194304K, 0% used [0x0000000680000000,0x0000000680039608,0x0000000780000000)
 PSPermGen       total 21504K, used 2589K [0x000000067ae00000, 0x000000067c300000, 0x0000000680000000)
  object space 21504K, 12% used [0x000000067ae00000,0x000000067b087668,0x000000067c300000)
 




答案现在一目了然:尽管我们总的堆大小是足够的,但堆中没有一个单独的区域能容下4G的对象。我们这个6G的堆被划分成了四个不同的区域,它们的大小分别是:


- Eden区:1536M
- Survivor区(包括from区及to区):分别是256M
- 老生代:4096M


现在,请记住了,对象只能在一个独立的区中进行分配,而现在我们看到,这个程序是不可能了——没有任何一个区能有足够的空间来满足这个单次的4G内存的分配的。


因此——我们只能寄希望于增加堆的大小了吗?尽管我们已经多提供了差不多50%的内存——为一个4G大小的数据结构提供了一个6G的堆?先别着急——还有另一个解决方案。你可以设置内存中不同的区域的大小。但这并不是你想像的那么简单明了,你需要对启动配置做两点小的改动。在运行这段相同的程序时,得多增加两个额外的参数:


My Precious:bin me$ java -Xms6g -Xmx6g -XX:NewSize=5g -XX:SurvivorRatio=10 eu.plumbr.demo.ArraySize
 




这样程序就能完成它的使命也不会抛出OutOfMemoryError了。启动时加上-XX:+PrintGCDetails参数的话可以印证这一点:


Heap
 PSYoungGen      total 4806144K, used 4369080K [0x00000006c0000000, 0x0000000800000000, 0x0000000800000000)
  eden space 4369408K, 99% used [0x00000006c0000000,0x00000007caaae228,0x00000007cab00000)
  from space 436736K, 0% used [0x00000007e5580000,0x00000007e5580000,0x0000000800000000)
  to   space 436736K, 0% used [0x00000007cab00000,0x00000007cab00000,0x00000007e5580000)
 ParOldGen       total 1048576K, used 0K [0x0000000680000000, 0x00000006c0000000, 0x00000006c0000000)
  object space 1048576K, 0% used [0x0000000680000000,0x0000000680000000,0x00000006c0000000)
 PSPermGen       total 21504K, used 2563K [0x000000067ae00000, 0x000000067c300000, 0x0000000680000000)
  object space 21504K, 11% used [0x000000067ae00000,0x000000067b080c90,0x000000067c300000)
 




我们可以看到,各个区的大小的确是我们想要的:


- 新生代的总大小是5G,确如-XX:NewSize=5g所指定的那样
- Eden区是Survior区的10倍,正如我们通过-XX:SurvivorRatio=10参数指定的那样


注意,在这个例子中,两个参数都是必须的。如果你只指定了-XX:NewSize=5g的话,新生代划分成eden和survivor区的结果就没有一个能大于4G的区域了。


希望这篇文章将来能省掉你一天的调试时间。或者让你避免提供不必要的资源(分配过大的Java堆)。


译注:仔细观察了下他的日志可以发现,他分配6G内存的时候,新生代占了大概2G,老生代刚刚好是4G的大小,并且程序退出的时候老生代中还占用了200多K的大小,这样正好存放不下这个数组。作者这个解决方法也有点奇怪,还有一个方案就是将新生代的大小稍微调小一点,比如-XX:MaxNewSize=1800M,这样的话这个数组就可以直接在老生代中进行分配了:


 ParOldGen       total 4448256K, used 4194304K [0x0000000680000000, 0x000000078f800000, 0x000000078f800000)
  object space 4448256K, 94% used [0x0000000680000000,0x0000000780000010,0x000000078f800000)
 



原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接
8
1
分享到:
评论
10 楼 Tyrion 2014-05-20  
全文重点是“请记住了,对象只能在一个独立的区中进行分配,而现在我们看到,这个程序是不可能了——没有任何一个区能有足够的空间来满足这个单次的4G内存的分配的。 ”,举的例子比较极端,真正生产上SurvivorRatio的值记得有个公式的。
9 楼 deepinmind 2014-05-20  
caizi12 写道
white_crucifix 写道
可能作者不太愿意把4G的对象放在老年代吧,毕竟老年代撑爆了做一次full GC代价不小


新生代的gc频率都比较高,这么大个对象放在新生代也不是太合适,这样的话老年代就要设置比4g还要大才行,尽量减少full gc。


你说的这个很对。原文底下有个评论提到了,如果这么大的对象生命周期还较长的话,老年代也得设置到4G以上,至少得分配8,9G内存了。
8 楼 deepinmind 2014-05-20  
white_crucifix 写道
可能作者不太愿意把4G的对象放在老年代吧,毕竟老年代撑爆了做一次full GC代价不小


嗯,也许这个4G的对象生命周期比较短,下一次young gc就回收掉了。
7 楼 caizi12 2014-05-20  
引用
My Precious:bin me$ java -Xms6g -Xmx6g -XX:NewSize=5g -XX:SurvivorRatio=10 eu.plumbr.demo.ArraySize


另外对这个有些疑问,指定了新生代为5g,SurvivorRatio=10那就是eden:Survivor=10:1,eden=4.16G,按jvm默认规则大对象都是直接进入老年代的,按lz的说法是已经没有oom了,难道是jvm会根据实际情况此时并不会把大对象直接放入老年区吗?
6 楼 white_crucifix 2014-05-19  
caizi12 写道
white_crucifix 写道
可能作者不太愿意把4G的对象放在老年代吧,毕竟老年代撑爆了做一次full GC代价不小


新生代的gc频率都比较高,这么大个对象放在新生代也不是太合适,这样的话老年代就要设置比4g还要大才行,尽量减少full gc。


嗯,其实这么大对象,放哪里都不合适啦。
如果按照原文的意思推测的话,场景的确是非常特殊。首先,这个大对象的运行范围非常小,使用后立刻丢失引用,能够严格控制一次Minor GC就能马上消灭,不然拷贝到老年代的过程要人命。
若按照这个前提,那不愿意直接放入老年代也可以理解。老年代GC少,如果一直没有突破Full GC,这4G可就长年屯在那边也没什么用处吧。
5 楼 caizi12 2014-05-19  
white_crucifix 写道
可能作者不太愿意把4G的对象放在老年代吧,毕竟老年代撑爆了做一次full GC代价不小


新生代的gc频率都比较高,这么大个对象放在新生代也不是太合适,这样的话老年代就要设置比4g还要大才行,尽量减少full gc。
4 楼 caizi12 2014-05-19  
white_crucifix 写道
可能作者不太愿意把4G的对象放在老年代吧,毕竟老年代撑爆了做一次full GC代价不小


这么大的对象,默认情况下jvm肯定会要往老年代放的。
3 楼 white_crucifix 2014-05-19  
可能作者不太愿意把4G的对象放在老年代吧,毕竟老年代撑爆了做一次full GC代价不小
2 楼 deepinmind 2014-05-19  
在世界的中心呼喚愛 写道
Heap
 PSYoungGen      total 4806144K, used 4369080K [0x00000006c0000000, 0x0000000800000000, 0x0000000800000000)
  eden space 4369408K, 99% used [0x00000006c0000000,0x00000007caaae228,0x00000007cab00000)
  from space 436736K, 0% used [0x00000007e5580000,0x00000007e5580000,0x0000000800000000)
  to   space 436736K, 0% used [0x00000007cab00000,0x00000007cab00000,0x00000007e5580000)
 ParOldGen       total 1048576K, used 0K [0x0000000680000000, 0x00000006c0000000, 0x00000006c0000000)
  object space 1048576K, 0% used [0x0000000680000000,0x0000000680000000,0x00000006c0000000)
 PSPermGen       total 21504K, used 2563K [0x000000067ae00000, 0x000000067c300000, 0x0000000680000000)
  object space 21504K, 11% used [0x000000067ae00000,0x000000067b080c90,0x000000067c300000)
 
这段我没有


得再加上一个参数指定-Xloggc:gc.log指定下输出文件才行
1 楼 在世界的中心呼喚愛 2014-05-18  
Heap
 PSYoungGen      total 4806144K, used 4369080K [0x00000006c0000000, 0x0000000800000000, 0x0000000800000000)
  eden space 4369408K, 99% used [0x00000006c0000000,0x00000007caaae228,0x00000007cab00000)
  from space 436736K, 0% used [0x00000007e5580000,0x00000007e5580000,0x0000000800000000)
  to   space 436736K, 0% used [0x00000007cab00000,0x00000007cab00000,0x00000007e5580000)
 ParOldGen       total 1048576K, used 0K [0x0000000680000000, 0x00000006c0000000, 0x00000006c0000000)
  object space 1048576K, 0% used [0x0000000680000000,0x0000000680000000,0x00000006c0000000)
 PSPermGen       total 21504K, used 2563K [0x000000067ae00000, 0x000000067c300000, 0x0000000680000000)
  object space 21504K, 11% used [0x000000067ae00000,0x000000067b080c90,0x000000067c300000)
 
这段我没有

相关推荐

    was6.1+AIX+ORACLE下一次OutOfMemoryError的解决过程

    在AIX 5.3操作系统下,使用WebSphere Application Server (WAS) 6.1和Oracle 10g的技术平台上,一个J2EE应用程序出现了OutOfMemoryError异常,导致整个系统挂死。该问题发生在项目上线大约半年之后,随着用户数量的...

    java.lang.OutOfMemoryError处理错误

    1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据。 2. 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。 3. 代码中存在死循环或循环产生过多重复的对象实体。 4. 使用的第三方软件中的BUG。 ...

    Myeclipse下java.lang.OutOfMemoryError Java heap space的解决

    - **大量数据处理**:程序一次性加载或处理的数据量过大,超过了JVM默认的堆内存大小。 - **配置不当**:Myeclipse中的JVM参数设置不合理,导致分配给程序使用的堆内存不足。 #### 二、解决方案 针对上述问题,...

    解决OutOfMemoryError内存溢出

    - 特别是在大数据量处理场景下,如大量查询返回结果时,如果一次性加载过多数据到内存,可能会导致内存溢出。 #### 具体案例分析 1. **Hibernate查询优化**: - 在使用Hibernate进行数据库查询时,如果一次性...

    java内存机制及异常处理

    新生对象在Eden区创建,经过一次或多次垃圾回收后仍存活的对象会晋升到老年代。这种方法降低了垃圾回收的开销,因为大部分对象生命周期较短,集中在年轻代回收。 Java内存监控工具如JConsole、jVisualVM可以帮助...

    一次使用Eclipse Memory Analyzer分析Tomcat内存溢出

    当应用程序遭遇 `OutOfMemoryError` 异常时,通常意味着程序存在内存管理问题,如内存泄露或内存溢出。本文将详细介绍如何利用 Eclipse Memory Analyzer (MAT) 工具来诊断并解决 Tomcat 服务器上的 Java 应用程序...

    完美解决java.lang.OutOfMemoryError处理错误的问题

    5. **内存分析工具**:使用如MAT(Memory Analyzer Tool)、VisualVM等工具进行内存分析,查找内存泄漏或异常对象增长。 6. **优化策略**:实施内存优化策略,如对象池、数据分页加载、及时关闭数据库连接等。 案例...

    无私奉献-jvm面试备战

    新生代的对象在经历过一次 GC 后,如果仍然存活,就会被转移到老年代中。 3. JVM 中的方法区: JVM 中的方法区也称为永久代(持久代)。在 JDK8 中,永久代被完全弃用,取而代之的是元数据区(Metaspace,元空间)...

    java.lang.OutOfMemoryError 错误整理及解决办法

    1. **大量数据加载**:当程序试图一次性加载大量数据,比如从数据库中取出过多记录,可能会耗尽内存。 2. **未清除的引用**:集合类如ArrayList、HashMap等如果在使用后未正确清理,它们会保留对对象的引用,阻止...

    记录一次Java爆掉之后的日志

    标题“记录一次Java爆掉之后的日志”暗示了这是一个关于Java程序运行时出现严重问题的案例,可能是由于内存溢出、线程死锁或者系统资源耗尽等原因导致的程序崩溃。这种问题通常会生成一个名为“hs_err_pid[进程ID]....

    Tomcat内存溢出的解决方法(java.util.concurrent.ExecutionException)

    "java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError" 是一个典型的错误提示,它表明在并发执行过程中遇到了内存不足的问题。下面我们将深入探讨这个问题的原因、影响以及如何解决。 内存溢出...

    Java常见内存溢出异常分析与解决

    - 大量数据加载:一次性从数据库中获取大量数据可能导致内存不足。 - 集合类未清理引用:如果集合类中的对象引用没有被正确清理,JVM无法进行垃圾回收。 - 死循环或过多重复对象:持续创建对象或无限递归可能导致...

    ArrayList源码分析(含jdk1.8).pdf

    DEFAULTCAPACITY_EMPTY_ELEMENTDATA是jdk1.8新引入的空参构造时使用的数组实例,用于延迟初始化容量到第一次添加元素时。elementData是一个transient修饰的Object数组,用于存放集合的元素。size用于记录集合中元素...

    项目多次重复发布经常出现一个 perman space 异常,解决方法

    标题提到的“perman space异常”通常表现为`java.lang.OutOfMemoryError: PermGen space`。 这个问题在Java 8中得到了根本性的改变,因为Oracle引入了新的元空间(Metaspace)来替代永久代。元空间主要存储类的元...

    一次OOM问题排查过程实战记录

    * 为什么 Tomcat 就会一次性分配这么大的 buffer 呢? * 为什么会有那么多的 Tomcat 线程? 通过进一步的分析,我们可以确定这些问题的答案。 六、结论 在本次 OOM 问题排查中,我们通过分析 Histogram、byte ...

    OutOfMemory的一个解决方法

    在Java编程中,`OutOfMemoryError`是一种常见的运行时异常,它通常发生在JVM试图为新对象分配内存时,但堆内存空间已满或无法进一步扩展的情况下。这个错误可以分为几种类型,包括`PermGen space`、`Heap Space`、`...

    Java内存溢出解决办法

    3. **优化代码**:减少不必要的对象创建,及时释放不再使用的资源,优化数据结构,避免大数据量一次性加载等。 4. **调整JVM参数**:根据应用需求合理配置JVM内存参数,避免内存分配不足或过大导致的浪费。 5. **...

    Dalvik虚拟机垃圾收集(GC)过程分析.docx

    - **GC_BEFORE_OOM**:在抛出OutOfMemoryError异常之前最后一次尝试回收内存。 #### GcSpec结构体解析 为了更好地理解和控制这些不同类型的垃圾收集,Dalvik虚拟机定义了一个名为`GcSpec`的结构体。该结构体用于...

Global site tag (gtag.js) - Google Analytics