JVM学习之:你了解OutOfMemoryError吗?


工作中或者在自己写的Demo中经常会出现OutOfMemoryError,从字面的意思看就是内存不够用了,可是往往越是经常看到的问题越不会留心的去观察他,就我本人而言OutOfMemoryError就是一个很好的例子,如果我事先知道某段程序会抛出这个错误,我会习惯性的用下面的语句去处理它:

try{
    do sth will throw OutOfMemoryError
}catch(Exception ex){
     ex.printStackTrace();
}

如果你和我又一样的想法,那么你可能需要注意了,因为OutOfMemoryError是属于Error并不是Exception,它的类继承关系是OutOfMemoryError->VirtualMachineError->ErrorThrowable,所以如果你用上面的block去处理它,根本就不会CATCH到异常,需要用下面的方式来处理

try {
     ......
} catch (Throwable ex) {
    ex.printStackTrace();
}

说明了它是什么之后,接下来我们来看看它都分哪几类?刚开始工作那时,对我而言OutOfMemoryError属于比较深奥的问题,每当从错误中看到OutOfMemoryError时,对错误的观察基本到此为止,殊不知真个错误真正的秘密还在后面,下面我列出了一些比较常见的原因以及错误的表现,在每种错误中我会加上我对他们的分析,如果有不恰当的或者是不正确的,还希望大虾门帮忙补充

一:堆 的OutOfMemoryError

总所周知,堆是真正用来存放对象实例的地方,每个应用的背后都对应着千千万万的对象实例,理论上这个地方也是OutOfMemoryError出现的高发地(有了GC的存在,保证这个地方不是那么的高发),下面的例子通过不断的创建对象,并且为了防止新生成的对象被GC回收掉,特意把产生的对象放到一个List中,然后为了更加容易的出现想要的效果,修改了虚拟机的参数(-Xms20m -Xmx20m),程序如下

public class OutofMemeorySample {
    
    public static void main(String[] args) {
        headOutOfMemory();
    }
    
    /*
     * -verbose:gc -XX:+PrintGCDetails -verbose:gc
     * -XX:+HeapDumpOnOutOfMemoryError
     *
     * -Xms20m -Xms20m
     *
     */
    static void headOutOfMemory() {
        long count = 0;
        try {
            
            List<Object> objects = new ArrayList<Object>();
            while (true) {
                count++;
                objects.add(new Object());
            }
        } catch (Throwable ex) {
            System.out.println(count);
            ex.printStackTrace();
        }
    }
    
}

通过程序可以发现,当Xmx的大小发生改变时,出现错误时的count值也在改变

出现的异常信息为:

java.lang.OutOfMemoryError: Java heap space

关键字Java heap space

二:栈的OutOfMemoryError

栈是存放主要是栈帧( 局部变量表(基本数据类型,对象引用,returnAddress类型), 操作数栈, 动态链接, 方法出口信息)的地方,当请求栈的空间大于了虚拟机所允许的时候会抛出StackOverflowError,当不能申请到更多的内存时,会抛出我们熟悉的OutOfMemoryError,因为栈也是内存的一部分,所以栈的空间不够时,到底是内存太小了,还是栈被用的太多了?其实这两种都是同一件事情,只是为了更好的划分而已,下面有两个程序,分别对StackOverflowError,OutOfMemoryError进行描述

StackOverflowError:通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常

public class JVMStackSOF {
/**
     * (1) 在hotspot虚拟机中不区分虚拟机栈(-Xss)和本地方法栈(-Xoss),且只有对Xss参数的设置,才对栈的分配有影响
     *
     * (2)
     * 由于StackOverflowError->VirtualMachineError->Error
     * ->Throwable,所以catch的时候如果用Exception的话将捕获不到异常 Stack length 会随着-Xss的减少而相应的变小
     */
    private int    stackNumber1    = 1;
    public void stackLeck1() {
        stackNumber1++;
        stackLeck1();
    }
    
    public static void main(String[] args) {
        JVMStackSOF jvmStackSOF = new JVMStackSOF();
        try {
            
            jvmStackSOF.stackLeck1();
        } catch (Throwable ex) {
            System.out.println("Stack length:" + jvmStackSOF.stackNumber1);
            ex.printStackTrace();
        }
    }
    
}

异常信息:java.lang.StackOverflowError

OutOfMemoryError:因为虚拟机会提供一些参数来保证堆以及方法区的分配,剩下的内存基本都由栈来占有,又由于每个线程都有独立的栈空间(堆,方法区为线程共有),所以如果你把虚拟机参数Xss(每个线程的栈空间)调大了,那么可以建立的线程数量必然减少,下面的程序可以用来验证这点

public class JVMStackOOM {
    
    /**
     * (1)不停的创建线程,因为OS提供给每个进程的内存是有限的,且虚拟机栈+本地方法栈=(总内存-最大堆容量(X模型)-最大方法区容量(
     * MaxPermSize)),于是可以推断出,当每个线程的栈越大时,那么可以分配的线程数量的就越少,当没有足够的内存来分配线程所需要的栈空间时,
     * 就会抛出OutOfMemoryException
     * (2)由于在window平台的虚拟机中,java的线程是隐射到操作系统的内核线程上的,所以运行一下代码时,会导致操作系统假死(我就尝到了血的代价)
     */
    private static volatile int    threadNumber    = 0;
    
    public void stackLeakByThread() {
        while (true) {
            new Thread() {
                public void run() {
                    threadNumber++;
                    while (true) {
                        System.out.println(Thread.currentThread());
                    }
                }
            }.start();
        }
    }
    
    public static void main(String[] args) {
        JVMStackOOM jvmStackOOM = new JVMStackOOM();
        try {
            jvmStackOOM.stackLeakByThread();
        } catch (Throwable ex) {
            System.out.println(JVMStackOOM.threadNumber);
            ex.printStackTrace();
        }
    }
}

由于在window平台的虚拟机中,java的线程是隐射到操作系统的内核线程上的,所以运行一下代码时,会导致操作系统假死

异常信息如下:

java.lang.OutOfMemoryError:unable to create new native thread

三:方法区溢出

方法区主要存放 被虚拟机加载的类信息,常量,运行时常量池,静态变量,即时编译器编译后的代码等.

(运行时常量池:java语言并不要求常量常量一定要在编译器产生,运行时也可以把一些常量放入池中,例如String类的intern方法)

由于这个区域内存回收的主要目标是运行时常量池以及对类型的卸载,一般来说这个区的回收效率不是很高,为了演示这个区的异常,可以通过不断的产生类信息来以及调整-XX:PermSize=10m -XX:MaxPermSize=10m 两个参数来达到这个目的,下面是相关代码

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/*
 * 利用CGLib技术不断的生成动态Class,这些Class的信息会被存放在方法区中,如果方法区不是很大会造成方法去的OOM
 *
 *
 * -XX:PermSize=10m -XX:MaxPermSize=10m
 * */
public class MethodAreaOOM {
    static class Test {}
    
    public static void main(String[] args) {
        try{
            
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Test.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                
                @Override
                public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(arg0, arg2);
                }
            });
            System.out.println(enhancer.create());
            
        }
        }catch(Throwable th){
            th.printStackTrace();
        }
    }
}


异常信息:java.lang.OutOfMemoryError: PermGen space

关键字:PermGen space

以上就是我知道的一些溢出的形式,如有内容不恰当的地方欢迎更正.......

reference:

jvm参数介绍:

http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm


[原文链接](https://blog.csdn.net/eric_sunah/article/details/7849546)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值