Java常见内存溢出异常分析

Java虚拟机规范规定JVM的内存分为了好几块,好比堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆内存分为了三部分,新生代,老年代,持久带,其中持久带实现了规范中规定的方法区,而内存模型中不一样的部分都会出现相应的OOM错误,接下来咱们就分开来讨论一下。 

栈溢出(StackOverflowError) 

栈溢出抛出java.lang.StackOverflowError错误,出现此种状况是由于方法运行的时候栈的深度超过了虚拟机允许的最大深度所致。 

出现这种状况,通常状况下是程序错误所致的,好比写了一个死递归,就有可能形成此种状况。 下面咱们经过一段代码来模拟一下此种状况的内存溢出。  java

Java代码 
  1. import java.util.*;  
  2. import java.lang.*;  
  3. public class OOMTest{  
  4.    
  5.   public void stackOverFlowMethod(){  
  6.       stackOverFlowMethod();  
  7.   }  
  8.    
  9.   public static void main(String... args){  
  10.       OOMTest oom = new OOMTest();  
  11.       oom.stackOverFlowMethod();  
  12.   }  
  13.    
  14. }  


运行上面的代码,会抛出以下的异常:  web

引用

Exception in thread "main" java.lang.StackOverflowError 
        at OOMTest.stackOverFlowMethod(OOMTest.java:6) 


堆溢出(OutOfMemoryError:java heap space) 

堆内存溢出的时候,虚拟机会抛出java.lang.OutOfMemoryError:java heap space,出现此种状况的时候,咱们须要根据内存溢出的时候产生的dump文件来具体分析(须要增长-XX:+HeapDumpOnOutOfMemoryErrorjvm启动参数)。出现此种问题的时候有多是内存泄露,也有多是内存溢出了。 
若是内存泄露,咱们要找出泄露的对象是怎么被GC ROOT引用起来,而后经过引用链来具体分析泄露的缘由。 
若是出现了内存溢出问题,这每每是程序本生须要的内存大于了咱们给虚拟机配置的内存,这种状况下,咱们能够采用调大-Xmx来解决这种问题。 

下面咱们经过以下的代码来演示一下此种状况的溢出:  spring

Java代码 
  1. import java.util.*;  
  2. import java.lang.*;  
  3. public class OOMTest{  
  4.    
  5.         public static void main(String... args){  
  6.                 List<byte[]> buffer = new ArrayList<byte[]>();  
  7.                 buffer.add(new byte[10*1024*1024]);  
  8.         }  
  9.    
  10. }  


咱们经过以下的命令运行上面的代码:  数据库

Java代码 
  1. java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest  



程序输入以下的信息:  数组

引用

[GC 1180K->366K(19456K), 0.0037311 secs] 
[Full GC 366K->330K(19456K), 0.0098740 secs] 
[Full GC 330K->292K(19456K), 0.0090244 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
        at OOMTest.main(OOMTest.java:7) 


从运行结果能够看出,JVM进行了一次Minor gc和两次的Major gc,从Major gc的输出能够看出,gc之后old区使用率为134K,而字节数组为10M,加起来大于了old generation的空间,因此抛出了异常,若是调整-Xms21M,-Xmx21M,那么就不会触发gc操做也不会出现异常了。 

经过上面的实验其实也从侧面验证了一个结论:当对象大于新生代剩余内存的时候,将直接放入老年代,当老年代剩余内存仍是没法放下的时候,出发垃圾收集,收集后仍是不能放下就会抛出内存溢出异常了 

持久带溢出(OutOfMemoryError: PermGen space) 

咱们知道Hotspot jvm经过持久带实现了Java虚拟机规范中的方法区,而运行时的常量池就是保存在方法区中的,所以持久带溢出有多是运行时常量池溢出,也有多是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了咱们配置。当持久带溢出的时候抛出java.lang.OutOfMemoryError: PermGen space。 
我在工做可能在以下几种场景下出现此问题。 

1.使用一些应用服务器的热部署的时候,咱们就会遇到热部署几回之后发现内存溢出了,这种状况就是由于每次热部署的后,原来的class没有被卸载掉。 
2.若是应用程序自己比较大,涉及的类库比较多,可是咱们分配给持久带的内存(经过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。 
3.一些第三方框架,好比spring,hibernate都经过字节码生成技术(好比CGLib)来实现一些加强的功能,这种状况可能须要更大的方法区来存储动态生成的Class文件。 
咱们知道Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,若是存在直接返回对常量池中对象的引用,不存在的话,先把此字符串加入常量池,而后再返回字符串的引用。那么咱们就能够经过String.intern方法来模拟一下运行时常量区的溢出.下面咱们经过以下的代码来模拟此种状况:  tomcat

Java代码 
  1. import java.util.*;  
  2. import java.lang.*;  
  3. public class OOMTest{  
  4.    
  5.         public static void main(String... args){  
  6.                 List<String> list = new ArrayList<String>();  
  7.                 while(true){  
  8.                         list.add(UUID.randomUUID().toString().intern());  
  9.                 }  
  10.         }  
  11.    
  12. }  


咱们经过以下的命令运行上面代码: 
java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest 
运行后的输入以下图所示:  服务器

引用

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 
        at java.lang.String.intern(Native Method) 
        at OOMTest.main(OOMTest.java:8) 


经过上面的代码,咱们成功模拟了运行时常量池溢出的状况,从输出中的PermGen space能够看出确实是持久带发生了溢出,这也验证了,咱们前面说的Hotspot jvm经过持久带来实现方法区的说法。 

OutOfMemoryError:unable to create native thread 

最后咱们在来看看java.lang.OutOfMemoryError:unable to create natvie thread这种错误。 出现这种状况的时候,通常是下面两种状况致使的: 

1.程序建立的线程数超过了操做系统的限制。对于Linux系统,咱们能够经过ulimit -u来查看此限制。 
给虚拟机分配的内存过大,致使建立线程的时候须要的native内存太少。咱们都知道操做系统对每一个进程的内存是有限制的,咱们启动Jvm,至关于启动了一个进程,假如咱们一个进程占用了4G的内存,那么经过下面的公式计算出来的剩余内存就是创建线程栈的时候能够用的内存。 线程栈总可用内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存 经过上面的公式咱们能够看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的状况下,能够建立的线程数也就越小。所以若是是由于这种状况致使的unable to create native thread,那么要么咱们增大进程所占用的总内存,或者减小-Xmx或者-Xss来达到建立更多线程的目的。 多线程

1、内存溢出类型 app

一、java.lang.OutOfMemoryError: PermGen space 框架

JVM管理两种类型的内存,堆和非堆。堆是给开发人员用的上面说的就是,是在JVM启动时建立;非堆是留给JVM本身用的,用来存放类的信息的。它和堆不一样,运行期内GC不会释放空间。若是web app用了大量的第三方jar或者应用有太多的class文件而刚好MaxPermSize设置较小,超出了也会致使这块内存的占用过多形成溢出,或者tomcat热部署时侯不会清理前面加载的环境,只会将context更改成新部署的,非堆存的内容就会愈来愈多。

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不一样,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,因此若是你的应用中有很CLASS的话,就极可能出现PermGen space错误,这种错误常见在web服务器对JSP进行pre compile的时候。若是你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。 
一个最佳的配置例子:(通过本人验证,自从用此配置以后,再未出现过tomcat死掉的状况)

set JAVA_OPTS=-Xms800m -Xmx800m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m

二、java.lang.OutOfMemoryError: Java heap space

第一种状况是个补充,主要存在问题就是出如今这个状况中。其默认空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。若是内存剩余不到40%,JVM就会增大堆到Xmx设置的值,内存剩余超过70%,JVM就会减少堆到Xms设置的值。因此服务器的Xmx和Xms设置通常应该设置相同避免每次GC后都要调整虚拟机堆的大小。假设物理内存无限大,那么JVM内存的最大值跟操做系统有关,通常32位机是1.5g到3g之间,而64位的就不会有限制了。

注意:若是Xms超过了Xmx值,或者堆最大值和非堆最大值的总和超过了物理内存或者操做系统的最大限制都会引发服务器启动不起来。

垃圾回收GC的角色

JVM调用GC的频度仍是很高的,主要两种状况下进行垃圾回收:

当应用程序线程空闲;另外一个是java内存堆不足时,会不断调用GC,若连续回收都解决不了内存堆不足的问题时,就会报out of memory错误。由于这个异常根据系统运行环境决定,因此没法预期它什么时候出现。

根据GC的机制,程序的运行会引发系统运行环境的变化,增长GC的触发机会。

为了不这些问题,程序的设计和编写就应避免垃圾对象的内存占用和GC的开销。显示调用System.GC()只能建议JVM须要在内存中对垃圾对象进行回收,但不是必须立刻回收,

一个是并不能解决内存资源耗空的局面,另外也会增长GC的消耗。

2、JVM内存区域组成

简单的说java中的堆和栈

java把内存分两种:一种是栈内存,另外一种是堆内存

1。在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;

2。堆内存用来存放由new建立的对象和数组

在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的做用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理

堆的优点是能够动态分配内存大小,生存期也没必要事先告诉编译器,由于它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢;

栈的优点是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是肯定的无灵活性。

java堆分为三个区:New、Old和Permanent

GC有两个线程:

新建立的对象被分配到New区,当该区被填满时会被GC辅助线程移到Old区,当Old区也填满了会触发GC主线程遍历堆内存里的全部对象。Old区的大小等于Xmx减去-Xmn

java栈存放

栈调整:参数有+UseDefaultStackSize -Xss256K,表示每一个线程可申请256k的栈空间

每一个线程都有他本身的Stack

3、JVM如何设置虚拟内存

提示:在JVM中若是98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。

提示:Heap Size 最大不要超过可用物理内存的80%,通常的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。

提示:JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。

默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减小堆直到-Xms的最小限制。所以服务器通常设置-Xms、-Xmx相等以免在每次GC 后调整堆的大小。

提示:假设物理内存无限大的话,JVM内存的最大值跟操做系统有很大的关系。

简单的说就32位处理器虽然可控内存空间有4GB,可是具体的操做系统会给一个限制,

这个限制通常是2GB-3GB(通常来讲Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了

提示:注意:若是Xms超过了Xmx值,或者堆最大值和非堆最大值的总和超过了物理内存或者操做系统的最大限制都会引发服务器启动不起来。

提示:设置NewSize、MaxNewSize相等,"new"的大小最好不要大于"old"的一半,缘由是old区若是不够大会频繁的触发"主" GC ,大大下降了性能

JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;

由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。

解决方法:手动设置Heap size

修改TOMCAT_HOME/bin/catalina.bat

在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入如下行:

JAVA_OPTS="-server -Xms800m -Xmx800m -XX:MaxNewSize=256m"

4、性能检查工具使用

定位内存泄漏:

JProfiler工具主要用于检查和跟踪系统(限于Java开发的)的性能。JProfiler能够经过时时的监控系统的内存使用状况,随时监视垃圾回收,线程运行情况等手段,从而很好的监视JVM运行状况及其性能。

1. 应用服务器内存长期不合理占用,内存常常处于高位占用,很难回收到低位;

2. 应用服务器极为不稳定,几乎每两天从新启动一次,有时甚至天天从新启动一次;

3. 应用服务器常常作Full GC(Garbage Collection),并且时间很长,大约须要30-40秒,应用服务器在作Full GC的时候是不响应客户的交易请求的,很是影响系统性能。

由于开发环境和产品环境会有不一样,致使该问题发生有时会在产品环境中发生,一般可使用工具跟踪系统的内存使用状况,在有些个别状况下或许某个时刻确实是使用了大量内存致使out of memory,这时应继续跟踪看接下来是否会有降低,

若是一直居高不下这确定就由于程序的缘由致使内存泄漏。

5、不健壮代码的特征及解决办法

一、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。

对于仍然有指针指向的实例,jvm就不会回收该资源,由于垃圾回收会将值为null的对象做为垃圾,提升GC回收机制效率;

二、咱们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer,每个String对象都得独立占用内存一块区域;

String str = "aaa";

String str2 = "bbb";

String str3 = str + str2;//假如执行这次以后str ,str2之后再不被调用,那它就会被放在内存中等待Java的gc去回收,程序内过多的出现这样的状况就会报上面的那个错误,建议在使用字符串时能使用StringBuffer就不要用String,这样能够省很多开销;

三、尽可能少用静态变量,由于静态变量是全局的,GC不会回收的;

四、避免集中建立对象尤为是大对象,JVM会忽然须要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,并且申请数量还极大。

这是一个案例想定供你们警惕

使用jspsmartUpload做文件上传,运行过程当中常常出现java.outofMemoryError的错误,

检查以后发现问题:组件里的代码

m_totalBytes = m_request.getContentLength();

m_binArray = new byte[m_totalBytes];

问题缘由是totalBytes这个变量获得的数极大,致使该数组分配了不少内存空间,并且该数组不能及时释放。解决办法只能换一种更合适的办法,至少是不会引起outofMemoryError的方式解决。参考:http://bbs.xml.org.cn/blog/more.asp?name=hongrui&id=3747

五、尽可能运用对象池技术以提升系统性能;生命周期长的对象拥有生命周期短的对象时容易引起内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,能够考虑分块进行处理,而后解决一块释放一块的策略。

六、不要在常常调用的方法中建立对象,尤为是忌讳在循环中建立对象。能够适当的使用hashtable,vector 建立一组对象容器,而后从容器中去取那些对象,而不用每次new以后又丢弃

七、通常都是发生在开启大型文件或跟数据库一次拿了太多的数据,形成 Out Of Memory Error 的情况,这时就大概要计算一下数据量的最大值是多少,而且设定所需最小及最大的内存空间值。

相关文章
相关标签/搜索