Java虚拟机中各运行时区域OOM实例

Java堆溢出

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

 1 public class HeapOOM{
 2       static class OOMObject{
 3       }
 4       
 5       public static void main(String[] args){
 6            List<OOMObject> list = new ArrayList<OOMObject>();
 7            while(true){
 8                    list.add(new OOMObject()):
 9            }   
10      }    
11 }

  解决堆内的异常,一般手段先通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏还是内存溢出。

如果是内存泄漏, 可以进一步通过工具查看泄漏对象到GC Roots的引用链,于是就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们。

如果不存在内存泄漏,就是内存中的对象确实都还必须活着,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

 

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

  栈容量只由-Xss参数设定,在Java虚拟机规范中描述了两种异常:

  •   如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError异常
  •        如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常

  

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

   上述是在单线程情况下,无论是由于栈帧太大还是虚拟机栈容量太小,抛出的都是StackOverFlowError。还有一种因为创建线程导致的内存溢出异常,代码如下:

public class JavaVMStackOOM{    
    private void dontStop(){
        while(true){
            
        }
    }
    
    public void stackLeakByThread(){
        while(true){
            Thread thread = new Thread(new Runnable(){
                @Override
                public void run() {
                    dontStop();                
                }    
            });
            thread.start();
        }
    }
    
    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

方法区和运行时常量池溢出

  首先介绍String.intern()这个本地方法,它的作用是:如果字符串常量池中已经包含一个此String对象的字符串,则返回代表池中这个字符串的String对象,否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

  我们可通过-XX: PermSize 和 -XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量

public class RuntimeConstantPoolOOM{    
    public static void main(String[] args) {
        //使用List保持着常量池引用,避免Full GC回收常量池行为
        List<String> list = new ArrayList<String>();
        int i = 0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

 

 

  

  

posted @ 2019-03-13 20:56  TI_MO  阅读(445)  评论(0编辑  收藏  举报