搜索词条
1、idea报java.lang.OutOfMemoryError: Java heap space怎么解决?
2、java.lang.OutOfMemoryError: GC overhead limit exceeded怎么解决?
3、xssfworkbook导出Excel内存溢出?
4、如何查看jvm内存使用情况?
背景:使用POI导出海量数据内存溢出问题
应用配置:idea+tomcat7+informix+jdk7+poi3.9
本地机器的物理内存为8G,没有对JVM进行配置。
场景:从数据库查询大批量数据(其实就2万条数据),List存放2万条数据,利用poi,通过自己写的Excle数据导出工具,对数据进行处理,并以Excel文件的形式进行导出(会创建1个或多个sheet页)。创建Excle调用的方法如下(Utils中有Excle上传及导出工具,此处不再粘贴代码):
XSSFWorkbook wb = new XSSFWorkbook();
在导出Excle文件时,出现下述异常:
异常一:
严重: Servlet.service() for servlet [springMVC] in context with path [] threw exception [Handler dispatch failed; nested exception is
java.lang.OutOfMemoryError: Java heap space] with root cause
java.lang.OutOfMemoryError: Java heap space
在JVM中如果98%的时间是用于GC且可用的 Heap size 不足2%的时候将抛出此异常信息。JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置。
JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。
可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。如果Heap Size设置偏小,除了这些异常信息外,还会发现程序的响应速度变慢了。GC占用了更多的时间,
而应用分配到的执行时间较少。
Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。
Heap size的 -Xms -Xmn 设置不要超出物理内存的大小。否则会提示“Error occurred during initialization of VM Could not reserve enough space for object heap”。
当未对JVM进行配置时,出现异常一,原因如下:Excle工具采取poi框架对Excle进行导出导致的JVM内存溢出。原因是所创建的book sheet row cell 等,此时是存在内存的,并没有 持久化,那么随着数据量增大内存的需求量也就增大,那么很大可能就是要 OOM了。而我本地并未对JVM进行配置,均采用的默认配置。当导出2万条数据时,就导致了内存溢出。(测试了下,在准备金系统的开发环境也有该问题,测试环境与生产环境还未出现OOM问题(内存足够))。
然后我对JVM进行了配置(JVM模块会对JVM调优进行详细说明):
-Xms512m -Xmx2048m -Xss1024K
在进行导出时,出现了下述异常:
异常二:
java.lang.OutOfMemoryError: GC overhead limit exceeded
出现该错误的原因是因为垃圾回收为了释放较小的空间而占用了大量时间造成的。通常来说,当程序用98%的时间回收了不到2%的堆内存时导致的。通常是设置的堆内存
太小,导致没有足够的内存。
解决方法
1、首先检查程序有没有死循环或者其他一些导致内存被大量占用的程序,如果确定程序没有问题,只是程序本身需要大内存时,通过设置增加内存。
2、添加jvm启动参数限制使用内存:-XX:UseGCOverheadLimit
方法:找到tomcat部署路径下./bin/catalina.sh文件,打开,并在
# OS specific support. $var _must_ be set to either true or false.
cygwin=false
的上面添加(分两种情况)
1> 在java1.8之前的版本中
JAVA_OPTS="-Xms512m -Xmx2048m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m -XX:-UseGCOverheadLimit"
2> java1.8版本
JAVA_OPTS="-Xmx12000m -XX:-UseGCOverheadLimit"
这是一种应付了事的解决方案, 就是不想抛出 “java.lang.OutOfMemoryError: GC overhead limit exceeded” 错误信息。
// 不推荐
-XX:-UseGCOverheadLimit
我们强烈建议不要指定该选项: 因为这不能真正地解决问题,只能推迟一点 out of memory 错误发生的时间,到最后还得进行其他处理。指定这个选项, 会将原来的
java.lang.OutOfMemoryError: GC overhead limit exceeded 错误掩盖,变成更常见的 java.lang.OutOfMemoryError: Java heap space 错误消息。
需要注意: 有时候触发 GC overhead limit 错误的原因, 是因为分配给JVM的堆内存不足。这种情况下只需要增加堆内存大小即可。
在大多数情况下, 增加堆内存并不能解决问题。例如程序中存在内存泄漏, 增加堆内存只能推迟产生 java.lang.OutOfMemoryError: Java heap space 错误的时间。
当然, 增大堆内存, 还有可能会增加 GC pauses 的时间, 从而影响程序的吞吐量或延迟。
如果想从根本上解决问题,则需要排查内存分配相关的代码. 简单来说,需要回答以下问题:
哪类对象占用了最多内存?
这些对象是在哪部分代码中分配的。
要搞清这一点, 可能需要好几天时间。下面是大致的流程:
获得在生产服务器上执行堆转储(heap dump)的权限。“转储”(Dump)是堆内存的快照, 可用于后续的内存分析. 这些快照中可能含有机密信息, 例如密码、信用卡账号等,
所以有时候, 由于企业的安全限制, 要获得生产环境的堆转储并不容易。
在适当的时间执行堆转储。一般来说,内存分析需要比对多个堆转储文件, 假如获取的时机不对, 那就可能是一个“废”的快照. 另外, 每执行一次堆转储, 就会对JVM进行
一次“冻结”, 所以生产环境中,不能执行太多的Dump操作,否则系统缓慢或者卡死,你的麻烦就大了。
用另一台机器来加载Dump文件。如果出问题的JVM内存是8GB, 那么分析 Heap Dump 的机器内存一般需要大于 8GB. 然后打开转储分析软件(我们推荐Eclipse MAT ,
当然你也可以使用其他工具)。检测快照中占用内存最大的 GC roots。详情请参考: Solving OutOfMemoryError (part 6) – Dump is not a waste。 这对新
手来说可能有点困难, 但这也会加深你对堆内存结构以及 navigation 机制的理解。
接下来, 找出可能会分配大量对象的代码. 如果对整个系统非常熟悉, 可能很快就能定位问题。运气不好的话,就只有加班加点来进行排查了。
参看链接:有详细说明:
我进行了下述配置(下面会对如何对服务进行JVM配置进行详细说明):
-Xms512m -Xmx2048m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m -XX:-UseGCOverheadLimit
虽然偶尔可以导出成功,但确实,大多数情况下报异常一。这也证实了上述所述的原因。
我通过java自带的jconcole工具查看了当进行导出时,JVM占用内存的变化情况,如下图:15:00之前是当数据量在1万条左右时,多次导出,堆内存使用量在1G浮动,但仍可以正常进行导出。15:00以后是当导出的数据条数达到2万后,当第一次启动服务,并导出时,可能会导出成功一次,往后均OOM。15:30是一段时间没进行导出操作后,对内存使用量才降下来的。(下面会详细说明怎么查看JVM的内存使用量)
通过上述分析,可以得出结论,导致内存溢出的原因,就是poi的Excel导出工具占用的内存太大。
解决方案
上述通过对异常的分析,我们找到了异常出现的原因,是poi在进行Excel导出时,占用了大量的内存空间。解决方案如下,两种方案:
第一种:当数据量并不是特别大的时候,可以通过设置JVM的内存,导出大批量的数据。但是当数据量达到百万后,只有通过方案二来解决。
第二种:通过SXSSFWorkbook来导出。
XSSFWorkbook wb = new XSSFWorkbook();
下面我们详细的分析两种解决方案。
方案一
上面已经有了,不再赘述,对参数进行简单说明:
堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在Java虚拟机启动时创建的。”“在JVM中堆之
外的内存称为非堆内存(Non-heapmemory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用
的;非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
堆内存分配
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增
大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
JVM内存限制(最大值)
首先JVM内存限制于实际的最大物理内存(废话!呵呵),假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可
控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。
主要通过以下的几个jvm参数来设置堆内存的:
-Xmx512m 最大总堆内存,一般设置为物理内存的1/4
-Xms512m 初始总堆内存,一般将它设置的和最大堆内存一样大,这样就不需要根据当前堆使用情况而调整堆的大小了
-Xmn192m 年轻带堆内存,sun官方推荐为整个堆的3/8
-Xss 为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M
堆内存的组成 总堆内存 = 年轻带堆内存 + 年老带堆内存 + 持久带堆内存
年轻带堆内存 对象刚创建出来时放在这里
年老带堆内存 对象在被真正会回收之前会先放在这里
持久带堆内存 class文件,元数据等放在这里
-XX:PermSize=128m 持久带堆的初始大小
-XX:MaxPermSize=128m 持久带堆的最大大小,eclipse默认为256m。如果要编译jdk这种,一定要把这个设的很大,因为它的类太多了。
关于JVM的调优及配置说明等等,网上的各种资料、博客繁杂庞多,看的我是头昏眼花,还有一大部分是搬运工,没有自己做过配置,理解。这里不再进行代码搬运。关于JVM的调优应该是一个专门的课题来研究,下面我会自己看资料,写专门的博客。现在只是解决我在项目中遇到的问题,不做深究。
方案二
使用SXSSFWorkbook 导出进行数据导出。详见链接:
但是这种方式没有自定义颜色的方式。
自己的Excel文件导出工具,详见链接:
延伸
1、频繁full gc有什么影响?(JVM调优模块)
2、JVM 内置的通用垃圾回收原则。(JVM调优模块)
3、如何配置JVM的内存。
4、JVM调化。(JVM调优模块)
5、导致OOM的三种可能原因?
6、如何查看JVM的内存?
如果错过太阳时你流了泪,那你也要错过群星了。
在所有的矛盾中,要优先解决主要矛盾,其他矛盾也就迎刃而解。
不要做个笨蛋,为失去的郁郁寡欢,聪明的人,已经找到了解决问题的办法,或正在寻找。