改啥呢?写bug!手写jvm中的各种OOM
改啥呢?写bug!手写jvm中的各种OOM
温安适 发表于6个月前
改啥呢?写bug!手写jvm中的各种OOM
  • 发表于 6个月前
  • 阅读 161
  • 收藏 3
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

 前言

    大家好,这篇blog不写什么实际技术,就把我从书上学来的,制造JVM各种OOM的方法告诉大家。下回在遇到有人问你Java会内存溢出吗?你可以快速回答他,会!我还会写各种bug,造成JVM出现OOM异常。

知己知彼,JVM的各个区域的特定

要想写出各种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

 逐个击破,实战各种OOM

下面的例子均来自《深入理解Java虚拟机第二版本》,不过我结合了IDEA进行了实战操作,对设置参数进行了注释,也算是精炼了实战OOM的方法。

java堆(GC堆)的OOM

java堆出现OOM的情况如下:

    堆中没有内存完成实例分配时,并且堆无法再扩展时。将抛出OutOfMemoryError。

为了让java堆(GC堆)更容易出现OOM,我们需要把JVM的堆内存分配的小一点,需要用到的参数如下:

  -Xms20m (JVM初始分配的堆内存)

  -Xmx20m(最大可使用内存)

  -XX:+HeapDumpOnOutOfMemoryError(r,JVM会在遇到OutOfMemoryError时拍摄一个“堆转储快照”)(可以不设置,对造成OOM没有帮助)

在IDEA中设置这些参数的方法如下:

1.先运行一遍写好的程序

2.之后在Run选项中选择Edit Configurations

之后如下图进行设置。

造成GC堆OOM的代码如下:

    其核心就是不断的生产对象,并保证已生产对象存活。利用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());
        }
    }
}

Java虚拟机栈OOM

虚拟机栈理论上有2种异常:
  1.StackOverflowError,线程请求的栈深度大于虚拟机所允许的深度。
  2.OutOfMemoryError栈扩展时申请到不足够的内存。

实验中,StackOverflowError非常容易出现,OutOfMemoryError从未出现过,网友可以尝试下。

下面仅介绍如何生成StackOverflowError。

为了让JVM,更容易出现StackOverflowError,我们需要设置如下参数:

-Xss128k(设置每个线程的堆栈大小 为128K)。设置方法如上,不在赘述。

造成虚拟机栈StackOverflowError的方法

这里利用死递归(没有出口的递归),不断的往虚拟机栈中加入递归上下文信息。

/**
 * 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内存溢出了!

 

 

标签: JVM Java
共有 人打赏支持
粉丝 48
博文 16
码字总数 25527
×
温安适
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: