zoukankan      html  css  js  c++  java
  • 模拟Java内存溢出

    本文通过修改虚拟机启动参数,来剖析常见的java内存溢出异常(基于jdk1.8)。

    修改虚拟机启动参数Java堆溢出虚拟机栈溢出方法区溢出本机直接内存溢出

    修改虚拟机启动参数

      这里我们使用的是IDEA集成开发环境,选择Run/Debug Configurations


      然后选择Configuration,修改VM options配置,就可以修改虚拟机启动参数了,本文的示例代码doc注释部分将会给出需要设置的虚拟机参数。

    Java堆溢出

     1import java.util.ArrayList;
    2import java.util.List;
    3
    4/**
    5 * 堆溢出测试.
    6 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
    7 * -XX:HeapDumpPath=/Users/lijl/Desktop
    8 *
    9 * @author jialin.li
    10 * @date 2020-04-08 10:02
    11 */

    12public class HeapOOM {
    13    public static void main(String[] args) {
    14        List<HeapOOM> list = new ArrayList<>();
    15
    16        while (true) {
    17            list.add(new HeapOOM());
    18        }
    19    }
    20}

      这里简单解释一下代码,我们通过-xms20m -Xmx20m两个参数,限制了Java堆的大小为20MB,不可扩展,后两个参数控制了当出现了OutOfMemoryError时,会Dump出当前内存的堆转储快照,并保存到指定位置中。
      接下来我们可以使用jdk自带的VisualVM来打开快照文件。
      命令行输入jvisualvm,点击左上角的装入,选中我们dump出来的堆快照文件。


      经过重新加载的堆内存记录如下:

      这里可以很直观的看出,OutOfMemoryError产生的原因,是HeapOOM这个对象导致的。
      解决问题的思路是:首先我们要排除内存泄露,即我们不需要的对象没有被回收掉。我们要找到泄漏的对象是如何与GC Root进行关联的?从而准确定位出泄漏代码的位置,然后进行修改。
      如果不是内存泄漏,即堆中的对象必须存活,这个时候,我们可以通过调节虚拟机的堆参数(-Xms -Xmx),适当调大堆内存。但是在此之前,我们一定要检查一下代码是否存在优化的空间,如:是否存在某些对象的生命周期过长?是否可以使用享元模式减少对象数量?等等

     

    虚拟机栈溢出

     1/**
    2 * 栈溢出测试.
    3 * VM Args: Xss128k
    4 *
    5 * @author jialin.li
    6 * @date 2020-04-08 10:02
    7 */

    8public class StackSOF {
    9
    10    private static int stackLength = 1;
    11
    12    public static void main(String[] args) {
    13        stackLeak();
    14    }
    15
    16    private static void stackLeak(){
    17        try{
    18            stackLength++;
    19            stackLeak();
    20        }catch (StackOverflowError e){
    21            System.out.println("stack Length:" +stackLength);
    22            e.printStackTrace();
    23        }
    24    }
    25}

      StackOverflowError属于比较好排查的一种错误,有错误栈可以阅读,大部分出现这种错误,都是递归程序书写的问题,没有弄清楚什么时候需要return;结束递归。


      这里有一个有趣的现象,操作系统给每个进程分配的内存是有限的,在多线程的场景下,如果每个线程分配的栈内存过大,就会导致OOM,这个时候可以适当减少每个线程的栈内存,来解决溢出问题(这可能不是最好的办法,只是因为这是一种比较不符合直觉的解决问题方式,所以这里单独说一下)。

     

    方法区溢出

      方法区用来存放Class相关的信息,比如类名、访问修饰符、常量池、字段描述等等。我们可以用运行时产生的大量的类填满方法区,这里我们使用了gclib来操作字节码,maven坐标如下:

    1<!-- cglib -->
    2<dependency>
    3    <groupId>cglib</groupId>
    4    <artifactId>cglib</artifactId>
    5    <version>2.2.2</version>
    6</dependency>

    代码:

     1import net.sf.cglib.proxy.Enhancer;
    2import net.sf.cglib.proxy.MethodInterceptor;
    3
    4/**
    5 * 方法区溢出测试.
    6 * VM Args: -XX:PermSize=10k -XX:MaxPermSize=10k
    7 *
    8 * @author jialin.li
    9 * @date 2020-04-08 14:18
    10 */

    11public class JavaMethodAreaOOM {
    12    public static void main(String[] args) {
    13        while (true){
    14            Enhancer enhancer = new Enhancer();
    15            enhancer.setSuperclass(OOMObject.class);
    16            enhancer.setUseCache(false);
    17            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o, args));
    18        }
    19    }
    20
    21    static class OOMObject{
    22
    23    }
    24}

      如果你用的也是和我一样的jdk1.8,此时我们将没有办法得到OOM,因为在jdk1.8之后,PermGen已经被移除了,所以永久代的参数也被一同移除。方法区的静态变量和常量池并入堆中,而类的元信息放到元空间中,元空间是一块本地内存,所以它的最大可分配空间就是系统内存的最大可用空间。
      我们可以将参数改为:VM Args: -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m,再执行上述代码,发现即使在设置了元空间大小的情况下,仍然不会触发OOM,可见元空间可以有效解决方法区OOM问题(会触发元空间的垃圾回收策略)。

    本机直接内存溢出

      这种情况发生的比较少,直接内存的容量,我们可以通过-XX:MaxDirectMemorySize来指定,如果不指定,默认与堆的最大值一样。我们可以通过Unsafe提供的方法申请本地内存分配。

     1import sun.misc.Unsafe;
    2import java.lang.reflect.Field;
    3
    4/**
    5 * 本机内存溢出.
    6 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
    7 *
    8 * @author jialin.li
    9 * @date 2020-04-08 15:53
    10 */

    11public class DirectMemoryOOM {
    12    private static final int _1MB = 1024 * 1024;
    13
    14    public static void main(String[] args) throws IllegalAccessException {
    15        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    16        unsafeField.setAccessible(true);
    17        Unsafe unsafe = (Unsafe) unsafeField.get(null);
    18        while (true) {
    19            unsafe.allocateMemory(_1MB);
    20        }
    21    }
    22}

      如果你是IDE集成开发环境,可能会因为内存不足结束执行程序。

    1Process finished with exit code 137 

      最后,期待您的订阅和点赞,专栏每周都会更新,希望可以和您一起进步,同时也期待您的批评与指正!

  • 相关阅读:
    机器学习之KNN算法学习笔记
    机器学习之决策树算法学习笔记
    RNN神经网络
    深度学习笔记-Tensorflow(一)
    CNN卷积神经网络-tensorflow
    个人喜欢的网址
    CSP2020游记
    题解 GDFZOJ 2020普转提十连测day5
    讲义 GDFZOJ 【38】 动态规划基础3
    题解 GDFZOJ 【2314】 东风谷早苗
  • 原文地址:https://www.cnblogs.com/nedulee/p/12661371.html
Copyright © 2011-2022 走看看