现在的位置: 首页 > 综合 > 正文

java OutOfMemoryError简单分析

2017年08月09日 ⁄ 综合 ⁄ 共 2958字 ⁄ 字号 评论关闭

Java之OutOfMemoryError简单分析


  最近编码遇到了Java内存溢出的问题,所以就想顺便总结一下几种导致Java内存溢出的栗子,以及碰到Java内存溢出要如何去解决。

Java堆溢出


  Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达的路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆容量限制之后就会产生内存溢出的异常。

  下面通过一段代码进行堆内存溢出异常的测试。

public class HeapOOM{
  static class HeapOOM{
    }
   public static void main(String[] args)
  {
    List<OOMObject> list = new ArrayList<OOMObject>();
      While(1)
       {
           list.add(new OOMObject());
    }
}      
}

  代码中对Java堆的限制大小是20M,不可扩展。具体设置是在VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError。

  当有堆溢出异常时,在异常堆栈信息栏中会出现“java.lang.OutOfMemoryError”,然后会提示:“Java heap space”。

  解决思路:


  一般是通过内存映象分析工具对Dump出来的堆转储快照进行分析,重点是确认内存中对象是否是必要的,也就是先分清楚是Memory Leak(内存泄露)还是Memory OverFlow(内存溢出)。如果是内存泄露,可以通过工具检查泄露对象到GC Roots的引用链。然后就能进而定位出泄露代码的位置。

  如果不是内存泄露,也就是说内存中的对象都存活。那就检查虚机的参数,与物理机进行对比,看看是否可以调大,从代码检查是否存在生命周期过长的对象,尝试减少程序运行期间内存的消耗。

虚拟机栈和本地方法栈溢出


  由于在HotSpot虚拟机中并不区分虚拟机栈以及本地方法栈,因此虽然-Xoss(用于设置本地方法栈的大小)参数存在,但是实际上是无效的。栈容量只由-Xss参数设定。

  下面是虚拟机和本地方法栈OOM的测试代码:

public class JavaVMStackSOF{
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void  main(String[] args) {
        JavaVMStackSOF com = new JavaVMStackSOF();
        try{
            com.stackLeak();
        }
        catch (Throwable e){
            System.out.println("Stack Length" + com.stackLength);
            throw e;
        }
    }
}

  上述代码使用了-Xss=128k,减少了栈内存的容量,结果抛出StackOverflowError异常,异常出现时输出的堆栈深度相应的缩小。

  定义了大量的本地变量,增大此方法帧中本地变量表的长度,结果抛出StackOverflowError异常时输出的堆栈深度相应缩小。也就是说在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,都会抛出StackOverflowError异常。

  解决思路:


  如果使用虚拟机默认参数,栈深度在大多数情况下达到1000-2000没问题,正常的方法调用(含递归),这个深度完全够用,如果是建立多线程导致内存溢出,在不能减少线程数情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。

方法区以及运行时常量池溢出


  由于运行时常量池是方法区一部分,所以就放在一起了。

  下面上代码:

public class RuntimeConstantPoolOOM{
    public static void main(String[] args) {
        List<String> list  = new ArrayList<String>();
        int i =0;
        while(1)
            list.add(String.valueOf(i++).intern());
    }
}

  设置VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M。String.intern()是一个Native方法,它的作用是:如果字符串常量池已经包含了等于此String对象的字符串,则返回代表池中这个字符串的String对象:否则,将此String包含的字符串添加到常量池中,并且返回此String的引用。从结果中可以看到,在OutOfMemoryError后跟随的提示信息是:PermGen Space,说明运行时常量池属于方法区一部分。

  解决思路:


  方法区溢出是一种常见的内存溢出异常,一个类要被垃圾收集器回收掉,判断条件是比较苛刻的。在经常动态生成大量的Class应用中,需要特别注意类的回收状况,常见的场景有:大量jsp或动态生成jsp文件的应用、基于OSGI的应用以及使用GGLib字节码增强(当前许多主流框架Spring、Hibernate都会用到GGLib这类字节码技术)和动态语言。

本机直接内存溢出


  DirectMemory容量可以通过-XX:MaxDirectMemorySize指定,如不指定,默认与java堆最大值一样。

  下面贴代码:

public class DirectMemroyOOM{

    public static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception{
        Field unsafeField = Unsafe.class.getDeclareFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (1)
            unsafe.allocateMemory(_1MB);
    }
}

  指定VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M,代码越过了DirectByBuffer类,直接通过反射获取Unsafe实例进行内存分配,之所以这么做是因为虽然使用DirectByteBuffer分配内存也会抛出内存溢出异常,但是抛出的异常并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异常,真正申请内存的方法是unsafe.allocateMemory()。

  解决思路:


  由于此种内存溢出一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果发现OOM之后Dump文件很小,而程序中又直接或间接使用了NIO,那就可以考虑一下是不是本机内存直接溢出了。

  以上就是有关java内存溢出相关的总结了,如果有不对的地方,欢迎指出,大家一起交流。

  

  PS:本博客欢迎转发,但请注明博客地址及作者~

   博客地址:http://www.cnblogs.com/voidy/

   <。)#)))≦ 


转载自:http://www.cnblogs.com/voidy/p/4161616.html

抱歉!评论已关闭.