JVM阶段(3)-OutOfMemoryError异常

1.前言

前两节的内容,说实在的,书上讲的比较宏观,接下来就是看看OOM,从细节上分析吧。

第一节学的运行时数据区讲过了,可能出现OOM问题的区域。程序计数器是不可能出现的,堆是高发区,栈的话如果栈帧过大并且没有设置栈深度,可能会出现。方法区的话,也可能会出现,比如运行时常量池,还有类信息的加载。以及非运行时数据区直接内存影响堆。这些都是,接下来就细化代码写一下。

2.OOM举例

2.1 java堆溢出

2.1.1 代码

VM options:-Xmx30m -Xms30m -XX:+HeapDumpOnOutOfMemoryError

最大内存30M,最小内存30M,内存不够生成Dump文件

package com.bo.jvmstudy.scondchapter;

import java.util.ArrayList;

/**
 * @Auther: zeroB
 * @Date: 2022/8/26 20:24
 * @Description:
 */
public class HeadOOM {
    static class OOMObject{
        public int[] arr1 = new int[1024*1024];
    }
    public static void main(String[] args) {
        ArrayList<OOMObject> oomObjects = new ArrayList<>();
        while (true){
            oomObjects.add(new OOMObject());
        }
    }

}

就是不停创建对象,然后导致内存溢出的一个样例。

异常报错,并导出对应hprof文件。

这个内容不是很熟悉,不过也可以看出,int类型占比比较大

int数组类型的对象较多,点进去再看看。对象很明显是OOMObject类型对象比较多。并且可以看到GC ROOT来自于main方法。

可以找到内存溢出的对象。

2.2 java栈溢出

在java虚拟机规范中,记录了两种异常,主要针对于虚拟机栈以及本地方法栈是否支持动态扩展。

在不支持动态扩展的情况下,线程请求的栈深度大于虚拟机所允许的最大深度时,报stackoverflowError异常。

在支持动态扩展的情况下,则当扩展的栈容量要大于当前虚拟机提供的内存时,报OOM异常。

这两种方式是虚拟机层面的东西,我们平时使用的hotspot虚拟机,其底层是不支持动态扩展的。也就是说,除非在创建线程的时候,申请不下足够的内存,才会报OOM。否则,其余的,都是按stackoverflowError来走。

2.2.1 减小栈空间大小

可以通过-Xss来控制栈内存大小。其实控制的是线程内存空间所占用的大小,虚拟机栈,本地方法栈,程序计数器均是其内部的一部分。

package com.bo.jvmstudy.scondchapter;

/**
 * @Auther: zeroB
 * @Date: 2022/8/31 17:53
 * @Description: 就是无限递归,顺便控制一下栈大小 -Xss128k
 */
public class StackOverflowErrorTest {
    public static void main(String[] args) {
        //128k 栈深度差不多970左右,是上下浮动的
        //256k 2000多,依赖与内存大小
        int num = 0;
        stackflow(num);

    }

    private static void stackflow(int num) {
        num++;
        System.out.println("测试栈深度"+num);
        stackflow(num);
    }
}

这里的异常其实都是StackOverflowError,即限制了栈的高度,达到了上限。

2.2.2 创建过多无用变量

这种测试方式,创建很多变量也是报oom,因为代码太费事无用,不写了。

2.2.3 创建过多线程

我们平时所说的线程私有,线程公有(堆,方法区)。所以线程所分配的内存区域,是独立于方法区,以及堆的。

在操作系统中,分配给一个进程的内存是有限的。线程的空间=总内存-堆内存-方法区内存。(有没有其它零碎内存也没查过,先这么理解)。-Xss是控制线程大小的,所以意味着,如果-Xss设置的过大,虽然是虚拟内存,但注定创建的线程数量也会减少。内存也越容易耗尽。

package com.bo.jvmstudy.scondchapter;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Auther: zeroB
 * @Date: 2022/8/31 18:00
 * @Description: 虽然在书中,这块内容放到栈内存溢出这块内容,导致oom,设置JVM参数-Xmx,-Xms看来没法控制
 * -Xss 堆栈内存,其实也是分配的线程内存,默认是1M,这里的话是虚拟内存,就像程序计数器中线程私有的东西,都在这块内存的中包含
 */
public class CreateManyThreadOOM {
    public static void main(String[] args) {
        while(true){
            new Thread(new Runnable() {
                @Override
                public void run() {
                   while(true){

                   }
                }
            }).start();
        }
    }
}

2.3 方法区常量池溢出

想想,方法区中存放了什么,常量,静态变量,运行时常量池(jdk1.6),类信息等等。

原先的常量池在永久代中,也就是方法区。这里不测试了。在1.8时,永久代移除,元空间上位。

2.3.1 运行时常量池溢出

运行时常量池在堆中,证明一下。String内部的intern方法,如果常量池中没有该对象,将其加入倒常量池并返回该对象,否则返回常量池中的对象。

package com.bo.jvmstudy.scondchapter;

import java.util.HashSet;

/**
 * @Auther: zeroB
 * @Date: 2022/8/31 18:39
 * @Description: 静态方法区溢出,因为JVM静态方法区其实就是存放静态变量,常量,运行时常量池以及类信息这些,溢出的话,需要控制的是运行时常量池
 * 但在JDK7时运行时常量池存放到堆中,所以在现有情况下,肯定是heap溢出了。
 */
public class ConstantPoolOOM {
    public static void main(String[] args) {
        //6M报异常Java heap space   -Xmx6M -Xms6M
        //10M报异常 GC overhead limit exceeded  -Xmx10M -Xms10M
        HashSet<String> set = new HashSet<>();
        Integer num = 0;
        while(true){
            //将字段放入常量池中
            set.add(String.valueOf(num++).intern());
        }
        //想打印JVM运行时常量池信息,没查到

    }

}

6M的情况下,堆溢出,确实是在堆中。

String内部的intern方法,如果常量池中没有该对象,将其加入倒常量池并返回该对象,否则返回常量池中的对象。这句也证明一下。

package com.bo.jvmstudy.scondchapter;


/**
 * @Auther: zeroB
 * @Date: 2022/8/31 19:33
 * @Description: 就是看一下jdk8,运行时常量池已经移动到堆的证明
 */
public class JVM6And7RunConstantPoolDiff {
    public static void main(String[] args) {
        //这里为true,因为运行时常量池中没有该数据,所以运行时常量池中记录了首次出现字符的引用
        String s = new StringBuilder("德莱").append("联盟").toString();
        System.out.println(s.intern() == s);

        //false相当于重新拼接了一个字符串对象,和原先的地址肯定是不一样的,如果和s对比,那就一样了
        String s1 = new StringBuilder("德莱").append("联盟").toString();
        System.out.println(s1.intern() == s1);
        //true
        System.out.println(s1.intern() == s);

        String s2 = new StringBuilder("ja").append("va").toString();
        //false java在之前已经被加载到运行时常量池中
        System.out.println(s2.intern() == s2);
    }

}

2.3.2 方法区溢出

使用cglib代理,加载类信息,可以看到方法区溢出。因为1.8元空间,所以按元空间的参数来配置。

package com.bo.jvmstudy.scondchapter;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Auther: zeroB
 * @Date: 2022/8/31 19:42
 * @Description: 通过CGLIB代理来添加对象,这样就可以做到动态生成类信息,最终使类加载让静态方法区溢出
 */
public class CGLIBProxyStaticOOM {
    public static void main(String[] args) {
        //1.6情况下的场景咱不试了,-XX:MaxPermSize=10M -XX:PermSize=10M
        //用元空间设置-XX:MaxMetaspaceSize=10M -XX:MetaspaceSize=10M  Metaspace
        while(true){
            //cglib代理类
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Object.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invoke(o,args);
                }
            });
            enhancer.create();
        }
    }
}

上方是一个CGLib的动态代理生成,不断往内部加载类,报Metaspace OOM了。

2.4直接内存溢出

直接内存这块不太了解,先贴着代码,以后再看。

package com.bo.jvmstudy.scondchapter;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @Auther: zeroB
 * @Date: 2022/8/31 19:53
 * @Description: 直接内存溢出,这块其实不是很明白,直接内存的概念也不明白,只知道是本内存调度了堆内存,导致内存溢出?
 * Unsafe类我倒知道点,底层做CAS,这些都能操作倒
 */
public class DirectMemeryOOM {

    private static Integer memorySize = 1024 * 1024;

    public static void main(String[] args) throws IllegalAccessException {
        Field field = Unsafe.class.getDeclaredFields()[0];
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        int num = 0;
        while (true) {
            //直接error,无法捕获异常,直接内存还是不太明白,先记录一下
            /**
             * Exception in thread "main" java.lang.OutOfMemoryError
             * 	at sun.misc.Unsafe.allocateMemory(Native Method)
             * 	at com.bo.jvmstudy.scondchapter.DirectMemeryOOM.main(DirectMemeryOOM.java:24)
             */
            unsafe.allocateMemory(Long.valueOf(memorySize.toString()));
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值