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 list = new ArrayList();
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 list = new ArrayList();
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 ;
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 list = new ArrayList();
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 );
}
}