JVM:Java常见内存溢出异常分析

转载自:http://www.importnew.com/14604.htmlhtml

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

栈溢出(StackOverflowError)spring

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

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

public class OOMTest{
 
  public void stackOverFlowMethod(){
      stackOverFlowMethod();
  }
 
  public static void main(String... args){
      OOMTest oom = new OOMTest();
      oom.stackOverFlowMethod();
  }
}

 

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

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来解决这种问题。框架

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

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

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

java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTestspa

程序输入以下的信息:

1
2
3
4
5
[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方法来模拟一下运行时常量区的溢出.下面咱们经过以下的代码来模拟此种状况:

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

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

java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest

运行后的输入以下图所示:

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