JVM笔记(二)OutOfMemoryError与StackOverflowError异常

在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能

一、Java堆溢出

垃圾回收的标准是:GC Roots到对象之间没有可达路径即可回收。当对象数量到达堆所能容下的最大限制后就会产生内存溢出异常。
代码测试如下:

/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }

打印如下:
在这里插入图片描述
注:-Xms20m -Xmx20m :将堆的最小值和最大值设置为一样即可避免堆自动扩展,-XX:+HeapDumpOnOutOfMemoryError:让虚拟机在出现内存溢出时异常时Dump出当前内存堆转储快照以便事后进行分析(如使用的是idea,现安装JProfiler插件)。

如果只观察程序发生Minor GC和Full GC的次数以及时常,加载类数量、线程数量等简单数据,可以使用jconsole,使用方式如下:
在这里插入图片描述
分析:要解决这个区域的异常,可以通过内存映像分析工具,如profiler,对dump出来的堆转储快照进行分析先分清楚是内存泄漏还是内存溢出
1. 内存泄漏
通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象与GC Roots关联从而垃圾回收器无法回收该对象的原因,掌握泄漏对象的类型信息,以及GC Roots引用链的信息,从而定位到泄露代码的位置。
例如:这三行代码就会导致内存泄漏

While(true){
HashMap<String, String> hashMap = new HashMap<String, String>();
 list.add(hashMap);
         hashMap=null;
    }

2. 内存溢出
也就是说这些对象必须存活,应从三个方面考虑:分配的内存是否太小、从代码分析是否存在某些对象生命周期过长,持有状态时间过长的问题、创建的对象是否过多等
(1)若内存太小:可通过-Xms 与-Xmx调大内存的上限和下限(这要看是否有可用物理内存)
(2)对象生命周期过长:在对象不用的时候及时销毁,例如hashmap用完即毁不要占着内存活到代码结束。
(3)若对象太多:尽量使用单例模式,不要过多创建对象,否则既浪费资源又给垃圾回收造成压力。

二、虚拟机栈与本地方法栈溢出

HotSpot是不区分虚拟机栈和本地方法栈的,所以设置本地方法栈的-Xoss参数是不起作用的,栈容量由-Xss参数设置
Java虚拟机规范中描述了两种异常:
(1)如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverflowError异常。
(2)如果虚拟机在扩展栈时无法申请到足够的内存空间,会抛出OutOfMemoryError异常
但是以单线程为例:使用-Xss参数减少栈内存容量,抛出StackOverflowError异常。定义大量的本地变量,增加方法帧中本地方变量表的长度,同样会抛出StackOverflowError异常。两者异常输出时栈深度相应缩小。测试代码如下:

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

运行结果如下:
在这里插入图片描述
这说明当在单线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,内存无法分配,虚拟机抛出的都是StackOverflowError异常
实际上操作系统分配给每个进程的内存是有限制的,以win32为例,
虚拟机栈和本地方法栈内存=2G内存(操作系统限制)-Xmx(最大堆内存容量)-MaxPermSize(最大方法区容量)-计数器消耗内存 ,计数器消耗内存很小可以忽略。
由这个公式可得,每个线程分配的内存越大,可建立的线程越少。如果使用虚拟机默认参数,栈深度大多数情况下可达1000~2000,符合正常情况下使用,若建立过多线程导致内存溢出,可通过减少最大堆内存和栈容量解决。堆线程数量要求不大的情况下可以减少线程数。

三、本机直接内存溢出

可直接内存通过-XX:MaxDirectMemorySize指定,默认与java堆的最大值(-Xmx指定)一样。使用DirectByteBuffer分配内存时,它会现通过计算判断内存是否够用,若够用就会向操作系统申请分配内存,否则也会抛出内存溢出异常 。真正申请分配内存的方法是unsafe.allocateMemory()。

学习资料:《深入理解Java虚拟机:JVM高级特性与最佳实践》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值