【Java进阶之JVM异常】代码库(一)

DirectMemoryOOM.java

/**
 * DirectMemory 容量可以通过 -XX:MaxDirectMemorySize 指定,如果不指定,则默认与 Java 堆的最大值(-Xmx)一致。
 */

public class DirectMemoryOOM {
    private static final int _MB = 1024*1024;

    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0]; //反射
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_MB);
        }
    }
}

HeapOOM.java

/**
 * 堆溢出:
 * Java 堆用于存储对象实例,通过不断的创造对象,并且保证 GC Root 到对象之间有路径可达来避免垃圾回收机制清除这些对象,就可以造成在对象数量到达最大堆的容量限制后产生 
 * OutOfMemoryError 内存溢出异常。
 * 
 * 本实例限制 Java 堆大小为 20MB 不可扩展,10MB 分配给新生代,10M 分配给老年代,Eden 区和 Suivivor 区比为 8 比 1。
 * 即:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8,参数:-XX:+HeapDumpOnOutOfMemoryError 把虚拟机在出现内存溢出异常时 Dump 出当前的内存堆转储快照以便分析。
 */

public class HeapOOM {
    static class OOMObject {

    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

/**
 * Java 堆内存 OOM 异常是实际应用中最常见的内存溢出异常,要解决这个区域的异常,一般的手段是通过内存映像分析工具(如:Eclipse Memory Analyzer)对 Dump 出来的堆转
 * 存储快照分析,重点是确认内存中的对象是否必要的,也就是要分清楚到底是出现了内存泄露(Memory Leak)还是内存溢出(Memory Overflow)。
 *
 * 如果是内存泄露,进一步通过工具查看泄露对象到 GC Roots 的引用链,就能找到泄露对象是通过怎样的路径与 GC Roots 相关联并导致垃圾收集器无法自动回收的,就可以比较准确
 * 的定位出泄露代码的位置。
 *
 * 如果不存在泄露,换句话说就是内存中对象确实都还必须存活着,那就对照物理机器内存检查虚拟机堆参数 -Xmx 和 -Xms 看看是否还可以调大,同时,从代码上检查是否存在某些对象
 * 生命周期过长、持有状态过长的情况,尝试减少运行期间内存的消耗。
 */

Incremental.java

import java.util.List;
import java.util.ArrayList;

public class Incremental implements Runnable {
    List<Object> list = new ArrayList<Object>();

    public static void main(String[] args) {
        Thread t = new Thread(new Incremental());
        t.start();
    }

    public void run() {
        for (int i=1; i<1000000; i++) {
            Object o = new Object();

            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("This is the " + i + " Object!!!");
            list.add(o);
        }
    }
}

MethodAreaOOM.java

/**
 * 方法区溢出:
 * 方法区用于存放类的相关信息,如:类名、访问修饰符、常量池、字段描述、方法描述等,在主流框架如:Spring、Mybatis 对类增强时,通过 CGLib(Code Generation Library) 动态生成大量
 * 的运行时类,直至溢出。方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收掉,判定条件是非常苛刻的,在经常动态生成大量 Class 的应用中,需要特别注意
 * 类的回收状况。
 *
 * 同理,把 -XX:PermSize=10M 和 —XX:MaxPermSize=10M 设置为一样的大小。
 */

public class MethodAreaOOM {
    static class OOMObject {

    }

    public static void main(String[] args) {
        while (true) {
            Enhancer en = new Enhancer();
            en.setSuperClass(OOMObject.class);
            en.setUseCache(false);
            en.setCallback(new MethodInterceptor() {
                public Object intercept() throws Throwable {
                    return proxy.invokeSuper(obj, args);                  
                }
            });
            en.create();
        }
    }
}

/**
 * Caused by: java.lang.OutOfMemoryError: PermGen space
 *  at java.lang.ClassLoader.defineClass1(Native Method)
 *  at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
 *  at java.lang.ClassLoader.defineClass(ClassLoader.java.616)
 * ......
 */

MultiThreadStackOverflow.java

/**
 * 如果实验不限于单线程,通过不断的新建线程的方式也是可以产生内存溢出异常的,但是,这种方式产生的异常与栈帧的大小并没有必然联系。或者准确的说,在这种情况下,每个线程的栈分配的内存
 * 越大,反而越容易产生内存溢出异常。原因其实很好理解,操作系统分配给每个进程的内存是有限的,虚拟机提供了参数来控制 Java 堆和方法区内存的最大值(Xmx 和 MaxPermSize,PC 占内存
 * 很小忽略不计),同时把虚拟机进程本身消耗内存忽略不计,剩下的内存由虚拟机栈和本地方法栈瓜分,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时候就越容易把剩下的
 * 内存耗尽。
 *
 * 出现 StackOverflow 异常有堆栈信息,相对而言是比较容易找到问题所在。而且如果使用虚拟机默认参数,栈深度在大多数情况下达到 1000~2000 完全没
 * 问题,对于正常的方法调用(包括递归)完全够用了。但是,如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换 64 位的虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多
 * 的线程,这种通过"减少内存"的手段来解决内存溢出的方法,这在开发多线程应用时需要特别注意。
 */

public class MultiThreadStackOverflow implements Runnable {
    public static void main(String[] args) {
        while (true) {
            Thread th = new Thread(new MultiThreadStackOverflow());
            th.start();
        }
    }

    public void run() {
        int i = 0;
        while (true) {
            i++;
        }
    }
}

ReferenceCount.java

/**
 * 引用计数算法,简单高效,包括微软 COM(Component Object Model) 技术,ActionScript 3 的 FlashPlayer,Python 语言等都是使用该技术进行内存管理。
 * 但是,Java 并没有采用这项技术,最主要的原因是引用计数很难解决对象之间相互循环引用的问题,我们可以验证一下,如下例:GC 日志清楚的包含了 5427K->600K,
 * 意味着 JVM 并没有因为这两个对象相互引用就不回收,故 JVM 并不是通过引用计数算法来判断对象是否存活。
 */

public class ReferenceCountGC {
    public Objcet instance = null;
    private static final int _MB = 1024*1024;

    //这个成员变量的意义是占多点内存,便于在 GC 日志中看清楚是否被回收过
    private byte[] bigSize = new byte[2*_MB];

    public static void main(String[] args) {
        ReferenceCountGC objA = new ReferenceCountGC();
        ReferenceCountGC objB = new ReferenceCountGC();

        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        System.gc();
    }
}

/**
 * [GC (System.gc()) [PSYoungGen: 5427K->600K(38400K)] 5427K->608K(125952K), 0.0006499 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 * [Full GC (System.gc()) [PSYoungGen: 600K->0K(38400K)] [ParOldGen: 8K->498K(87552K)] 608K->498K(125952K), 
 * [Metaspace: 2509K->2509K(1056768K)], 0.0034147 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 *  Heap
 *  PSYoungGen      total 38400K, used 333K [0x00000000d5900000, 0x00000000d8380000, 0x0000000100000000)
 *  eden space 33280K, 1% used [0x00000000d5900000,0x00000000d59534a8,0x00000000d7980000)
 *  from space 5120K, 0% used [0x00000000d7980000,0x00000000d7980000,0x00000000d7e80000)
 *  to   space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
 *  ParOldGen       total 87552K, used 498K [0x0000000080a00000, 0x0000000085f80000, 0x00000000d5900000)
 *  object space 87552K, 0% used [0x0000000080a00000,0x0000000080a7c9b8,0x0000000085f80000)
 *  Metaspace       used 2516K, capacity 4486K, committed 4864K, reserved 1056768K
 *  class space    used 270K, capacity 386K, committed 512K, reserved 1048576K
 */

RuntimeConstantPoolOOM.java

/**
 * 向常量池中添加对象最简单的方法是 String.intern() 这个 Native 方法,若池中存在一个等于此 String 对象的字符串,则返回代表池中这个字符串的 String 对象,否则把此对象包含的
 * 字符串添加到常量池中,并返回此 String 对象的引用。本实例通过限制方法区大小为 10M 来间接限制常量池容量,即:-XX:PermSize=10 和 -XX:MaxPermSize=10
 */

public class RuntimeConstantPoolOOM {
    int i = 0;

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

SingleThreadStackOverflow.java

/**
 * 在 HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈,JVM 中描述了两种异常:
 *  1、如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
 *  2、如果虚拟机在扩展栈时无法申请到足够的内存空间,将抛出 OutOfMemoryError 异常。
 *  
 * 其实,当栈空间无法继续分配时,内存太小或占用栈空间太大,其本质都是对同一件事情的两种描述而已。如果把实验范围限制在单线程中,下面两种方法都无法
 * 让虚拟机产生 OutOfMemoryError 异常,都获得了 StackOverflowError 中描述了两种异常:
 *  1、使用 -Xss 参数减少栈内存容量,结果抛出 StackOverflowError 异常,异常出现时输出的栈深度相应缩小。
 *  2、定义了大量的本地变量,增加此方法帧中本地变量表的长度,结果抛出 StackOverflowError 异常,异常出现时输出的栈深度相应缩小。
 */

public class StackOverflow {
    private static int level = 0;

    public static void main(String[] args) {
        try {
            System.out.println(fac(1<<15));
        } catch (StackOverflowError e) {
            System.out.println("true recursive level: "+level);
            System.out.println("reported recursive level: "+e.getStackTrace().length);
            e.printStackTrace();
        }
    }

    private static long fac(int n) { //深度递归
        level++;
        return n<2 ? n : fac(n-1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值