大家好,这篇blog不写什么实际技术,就把我从书上学来的,制造JVM各种OOM的方法告诉大家。下回在遇到有人问你Java会内存溢出吗?你可以快速回答他,会!我还会写各种bug,造成JVM出现OOM异常。
要想写出各种OOM,必须知道JVM各个区域的特点,以便针对性的写bug,造成OOM。下面是我看书后总结的JVM各个区域的特点:
区域名称 | 作用 | 是否线程私有 | 是否会 内存溢出 | 溢出原因 |
---|---|---|---|---|
程序计数器 | 当前线程所执行的字节码的行号的指示器。 每个线程都有独立的程序计数器 | 是 | 否 | |
Java虚拟机栈 | 与线程同生命周期存储局部变量表,操作数栈 动态链接,方法出口,对象引用等。 局部变量表存储基本数据类型 boolean,int,float,long, double,byte,short. long,double占2其余占1局部空间 | 是 | 是 | 2种异常 1.StackOverflowError, 线程请求的栈深度大于虚拟机所允许的深度。 2.OutOfMemoryError 栈扩展时申请到不足够的内存。 |
本地方法栈 | Java虚拟机栈类似 为Native服务 | 是 | 是 | 2种异常 1.StackOverflowError,线程请求的栈深度大于 虚拟机所允许的深度。 2.OutOfMemoryError 栈扩展时申请到不足够的内存。 |
java堆 | 存放对象实例以及数组。 GC堆。 逻辑连续,物理不连续 通过-Xmx和-Xms来控制。 | 否 | 是 | 堆中内存不足,无法完成实例分配,并且堆无法再扩展时。 将抛出OutOfMemoryError |
方法区 | non-heap,“永久代” 受到-XX:MaxPermSize | 否 | 是 | 当方法区无法满足内存分配需求时OutOfMemoryError |
运行时常量池 | 字面量,符号引用。Java语言 不一定要求只有编译才会产生常量 String的interm()是方法区的一部分 JDK1.7将它从方法区移除,使用直接内存 | 否 | 是 | 受方法区限制(1.7以后不会).当常量池无法再申请到内存 时会内存溢出 |
直接内存 | 即本机直接内存,不受JVM控制。 | 否 | 是 | JVM各内存区域总和大于物理内存时, 当再动态扩展时会OutOfMemoryError |
下面的例子均来自《深入理解Java虚拟机第二版本》,不过我结合了IDEA进行了实战操作,对设置参数进行了注释,也算是精炼了实战OOM的方法。
java堆出现OOM的情况如下:
堆中没有内存完成实例分配时,并且堆无法再扩展时。将抛出OutOfMemoryError。
为了让java堆(GC堆)更容易出现OOM,我们需要把JVM的堆内存分配的小一点,需要用到的参数如下:
-Xms20m (JVM初始分配的堆内存)
-Xmx20m(最大可使用内存)
-XX:+HeapDumpOnOutOfMemoryError(r,JVM会在遇到OutOfMemoryError时拍摄一个“堆转储快照”)(可以不设置,对造成OOM没有帮助)
1.先运行一遍写好的程序
2.之后在Run选项中选择Edit Configurations
之后如下图进行设置。
其核心就是不断的生产对象,并保证已生产对象存活。利用List,维护所有OOMObject对象存活(利用list保存所有OOMObject都有引用),并利用集合自动扩展申请新的内存,直至Java堆剩余空间,不满足新的OOMObject对象所需的空间为止。
/**
* Created by Administrator on 2017/6/22.
* -Xms20m (JVM初始分配的堆内存)-Xmx20m(最大可使用内存)
* -XX:+HeapDumpOnOutOfMemoryError(r,JVM会在遇到OutOfMemoryError时拍摄一个“堆转储快照”)
*/
public class HeapOOM {
static class OOMObject{
}
public static void main(String[]args){
//利用List,维护所有OOMObject对象存活,并利用集合自动扩展申请新的内存。
List<OOMObject>list=new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
}
}
虚拟机栈理论上有2种异常: 1.StackOverflowError,线程请求的栈深度大于虚拟机所允许的深度。 2.OutOfMemoryError栈扩展时申请到不足够的内存。
实验中,StackOverflowError非常容易出现,OutOfMemoryError从未出现过,网友可以尝试下。
下面仅介绍如何生成StackOverflowError。
为了让JVM,更容易出现StackOverflowError,我们需要设置如下参数:
-Xss128k(设置每个线程的堆栈大小 为128K)。设置方法如上,不在赘述。
这里利用死递归(没有出口的递归),不断的往虚拟机栈中加入递归上下文信息。
/**
* Created by Administrator on 2017/6/22.
* -Xss128k(设置每个线程的堆栈大小 为128K)
*/
public class JavaVMStackSOF {
private int stackLength=1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF sof=new JavaVMStackSOF();
sof.stackLeak();
}
}
方法区存放类的相关信息,我们可以不断生成新的类信息到方法区,直到撑爆方法区。
如何动态产生类信息呢?JavaAPI中有反射, 但是很多框架中都是用CGLib,例如Spring。这里也使用CGLib.
其代码如下:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Created by Administrator on 2017/6/23.
*/
public class JavaMethodAreaOOM {
static class OOMObject{
}
public static void main(String[] args) {
while (true){
//创建增强器
Enhancer enhancer=new Enhancer();
//设置要增强的父类
enhancer.setSuperclass(OOMObject.class);
//不使用缓存很重要,保证更容易触发OOM
enhancer.setUseCache(false);
//不增强任何方法
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(o,objects);
}
});
//创建增强后的类
enhancer.create();
}
}
}
Java中何时会使用动态内存呢?NIO中的Channel,使用NIO比较繁琐,我们使用直接分配本地内存的方式,造成内存溢出。Unsafe类可以直接分配本地内存。
为了让容易造成本地内存直接溢出,我们设置参数:
--XX:MaxDirectMemorySize=10M(JVM使用本地内存上限为10M)
设置方式如上。
造成本地内存直接溢出的代码为:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
-Xmx20m --XX:MaxDirectMemorySize=10M
* Created by Administrator on 2017/6/23.
*/
public class DirectMemoryOOM {
private static final int _1MB=1024*1024;
public static void main(String[] args) throws IllegalAccessException {
//theUnsafe;d对象
Field unsafeField= Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
//但是如果字段是静态字段的话,传入任何对象都是可以的,包括null,这里获得theUnsafe对象
Unsafe unsafe= (Unsafe) unsafeField.get(null);
while (true){
//指派内存1MB
unsafe.allocateMemory(_1MB);
}
}
}
到此我们对JVM大部分区域,进行了针对性的OOM实战。有一些没有实战的,网友可以自行尝试。
查阅了这篇文章,我希望你可以很自豪的说,我能够写bug,造成JVM内存溢出了!