一次OutOfMemoryError异常的分析

news/2024/7/24 7:04:50 标签: java, OutOfMemoryError

        为什么我给JVM分配的堆已经足够大了,但在给一个数据结构分配内存的时候却抛出了OutOfMemoryError异常?这是我最近面临的一个问题。
        看了下开发人员这段代码到底是干什么的并且再三确认了通过-Xmx参数给JVM设置的堆大小之后,看样子问题确实是有点诡异了。
        通常来说,想弄清楚一个问题的最好方式就是通过一个实例来进行说明。这里我创建一个小的测试用例(由于我本机的JVM最大只能分配1444m内存,因此我将测试int的数组大小改小了):

java">class ArraySize {
	public static void main(String... args) {
		int[] array = new int[175 * 1024 * 1024];
	}
}

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

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


        首先,int类型在Java的确是占用4个字节。因此这并不是我们的JVM在晚上突然发疯了。而且我还可以很确定地告诉你,我的计算也没有问题——175*1024*1024个整型的确是需要734,003,200字节(700M),不到1G内存。
        想知道发生了什么,我们先使用–XX:+PrintGCDetails参数把GC的日志打开之后再运行一遍这个程序看看:


        答案现在一目了然:尽管我们总的堆大小是足够的,但堆中没有一个单独的区域能容下700M的对象。我们这个1G的堆被划分成了四个不同的区域,它们的大小分别是:
- Eden区:273.0625M(279616K)
- Survivor区(包括from区及to区):分别是34.125M(34944K)
- 老生代:682.6875M(699072K)
        现在,请记住了,对象只能在一个独立的区中进行分配,而现在我们看到,这个程序是不可能了——没有任何一个区能有足够的空间来满足这个单次的700M内存的分配。
        因此,我们只能寄希望于增加堆的大小了吗?尽管我们已经多提供了差不多50%的内存——为一个700M大小的数据结构提供了一个1G的堆?先别着急——还有另一个解决方案。你可以设置内存中不同的区域的大小。但这并不是你想像的那么简单明了,你需要对启动配置做两点小的改动。在运行这段相同的程序时,得多增加两个额外的参数:

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


        我们可以看到,各个区的大小的确是我们想要的:
        - 新生代的总大小是900M,确如-XX:NewSize=900M(768000K+76800K+76800K=921600K=900M)所指定的那样
       - Eden区是Survior区的10倍,正如我们通过-XX:SurvivorRatio=10参数指定的那样
        注意,在这个例子中,两个参数都是必须的。如果你只指定了-XX:NewSize=900m的话,新生代划分成eden和survivor区的结果就没有一个能大于700m的区域了。
       

        译文作者注(说明:译文作者和原文作者用的测试的数据大小与我上面的不同,他们的是:int[] array = new int[1024*1024*1024];  ):

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)

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

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

        按译文作者的注明:我共分配1100m内存,给新生区分配200m内存,这个数组就在老生代中进行分配了。


        实际上,新生代分配了366.625m(300416K+37504K+37504K=375424K=366.625M),老生代分配了733.375m(750976K=733.375M)(大于700M的对象内存)。

 

附:Eclipse中增加参数的方法

 


 

文章来源:http://it.deepinmind.com
英文原文链接


http://www.niftyadmin.cn/n/1318601.html

相关文章

form表单一些问题

1、input无法输入,可以尝试添加change 使用 this.$fouceUpdate()跟新视图,,,如果无效可以考虑在data中的form对象中声明一下例如:form { name: } 能确保name能正常输入,出现无法输入原因不是很清楚&#x…

网页调用qq聊天

<a target"_blank" href"http://wpa.qq.com/msgrd?v3&uin260186089&siteqq&menuyes"><img border"0" src"http://wpa.qq.com/pa?p2:260186089:53" alt"点击这里给我发消息" title"点击这里给我…

(转)SQL Server 2008无法修改表的解决办法

转自&#xff1a;http://www.soaspx.com/dotnet/sql/mssql/sql2008/sqlserver2008_20121010_9683.html 在SQL Server 2008 R2中新建一张数据表&#xff0c;然后需要修改表结构&#xff0c;修改完后却无法保存&#xff0c;弹出一个提示信息&#xff1a; 大概意思就是“不允许进行…

富养还是穷养,决定孩子的一生

是什么决定孩子未来物质能否丰盛&#xff1f;为什么说寒门很难出贵子&#xff0c;三代才能出贵族&#xff1f;真的是父母必须有钱&#xff0c;才能大概率保证孩子未来富有吗&#xff1f;-----作者&#xff1a;李雪爱与自由事实并非由物质决定&#xff0c;而是由心灵决定。一朋友…

shell脚本批量更新git/svn项目

直接打开工作目录终端 输入./updateData.sh即可看到批量更新项目 若无法生效可以 chmod 755 updateData.sh&#xff1b;非权限问题可以看下终端报错是否No such file or directory&#xff0c;检查一下工作目录的父级目录是不是英文名后面有空格 例如&#xff1a;abc 目录&a…

nodejs安装及环境配置(windows系统)

作为服务端运行javascript的平台的NodeJs&#xff0c;把前台javascript移到了服务器端&#xff0c;Google V8引擎使其运行效率非常高&#xff0c;它可以异步&#xff0c;无任何阻塞运行程序。nodejs包含http服务器&#xff0c;可以为我们实现 web系统设计&#xff0c;客户端jav…

el-table 实现下拉加载

1、npm install --save el-table-infinite-scroll 2、全局 import elTableInfiniteScroll from el-table-infinite-scroll; Vue.use(elTableInfiniteScroll); 3、局部组件 import elTableInfiniteScroll from el-table-infinite-scroll; export default { direct…

此类目的是防治序列化Json字符串时的循环引用问题-------最好解决方案

using Newtonsoft.Json;using System;using System.Collections.Generic;using System.Linq;using System.Web;namespace AccpStudentMIS{ //此类目的是防治序列化Json字符串时的循环引用问题 //此类为Object类的扩展方法&#xff0c;需要引用Newtonsoft.Json.dll类 /…