Java中常见内存溢出错误及处理方法

  相信有一定JAVA开发经验的人或多或少都会遇到OutOfMemoryError的问题,随着解决各类问题经验的积累以及对问题根源的探索,终于有了一个比较深入的认识。

  在解决Java内存溢出问题之前,需要对JVM(Java虚拟机)的内存管理有一定的认识。JVM管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)Java Stacks(Java栈)。其中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load的时候被放入PermGen space区域,Class需要存储的内容主要包括方法和静态属性。堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被JVM的垃圾回收机制管理。而Java栈跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:Permanent Generation spaceHeap space

导致java.lang.OutOfMemoryError异常的常见原因有以下几种:

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


一.java.lang.OutOfMemoryError: PermGen space

我在工作可能在如下几种场景下出现此问题。

使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。

如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。

一些第三方框架,比如spring,hibernate都通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。

发生这种问题的原意是程序中使用了大量的jarclass,使java虚拟机装载类的空间不够,与Permanent Generation space有关。

解决这类问题有以下两种办法:

增加java虚拟机中的XX:PermSizeXX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。如针对tomcat6.0,在catalina.sh catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右) 增加一行: JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m" 如果是windows服务器还可以在系统环境变量中设置。感觉用tomcat发布sprint+struts+hibernate架构的程序时很容易发生这种内存溢出错误。使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。

清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。


二.java.lang.OutOfMemoryError: Java heap space 

发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。

解决这类问题有两种思路:

检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。 我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了Java heap space的内存溢出问题,后来通过修改程序得到了解决

增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m


三.java.lang.OutOfMemoryError:unable to create natvie thread

 出现这种情况的时候,一般是下面两种情况导致的:

程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过ulimit -u来查看此限制。

给 虚拟机分配的内存过大,导致创建线程的时候需要的native内存太少。我们都知道操作系统对每个进程的内存是有限制的,我们启动JVM,相当于启动了一 个进程,假如我们一个进程占用了4G的内存,那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。 线程栈总可用内存=4G--Xmx的值)-XX:MaxPermSize的值)程序计数器占用的内存 通过上面的公式我们可以看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。因此如果是因为这种情况导致的unable to create native thread,那么要么我们增大进程所占用的总内存,或者减少-Xmx或者-Xss来达到创建更多线程的目的


四.java.lang.StackOverflowError  

栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候栈的深度超过了虚拟机容许的最大深度所致。出现这种情况,一般情况下是程序错误所致的,比如写了一个死递归,就有可能造成此种情况。 下面我们通过一段代码来模拟一下此种情况的内存溢出。


五.java.lang.OutOfMemoryError:GCoverheadlimitexceeded
JDK6新增错误类型,当GC为释放很小空间占用大量时间时抛出;一般是因为堆太小,导致异常的原因,没有足够的内存。
解决方案:
  查看系统是否有使用大内存的代码或死循环;
  通过添加JVM配置,来限制使用内存:
  -XX:-UseGCOverheadLimit

 

六.java.lang.OutOfMemoryError:Directbuffermemory
  调整-XX:MaxDirectMemorySize=参数,如添加JVM配置:
  -XX:MaxDirectMemorySize=128m

 

七.java.lang.OutOfMemoryError:unabletocreatenewnativethread
Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了。
解决方案:由于JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;而系统的用户空间一共是3G,除了Text/Data /BSS/MemoryMapping几个段之外,HeapStack空间的总量有限,是此消彼长的。因此遇到这个错误,可以通过两个途径解决:1.通 过-Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);2.通过-Xms- Xmx两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。

 

 

 


你可能感兴趣的:(Java核心,Java面试集)