Java JVM 内存溢出Oom

Java 虚拟机的默认内存分配:

-Xms 设置初始化默认内存,初始默认为物理内存的1/64

-Xmx最大分配内存,初始为物理内存的1/4

验证:

本机的实际内存为16G:

public class Test {
    public static void main(String[] args) {
        long maxMemory = Runtime.getRuntime().maxMemory();//1/4
        long totalMemory = Runtime.getRuntime().totalMemory();//1/64
        System.out.println("-Xmx:Max_Memory = " + maxMemory+"字节"+(maxMemory/(double)1024/1024)+"MB");
        System.out.println("-Xms:Total_Memory = " + totalMemory+"字节"+(totalMemory/(double)1024/1024)+"MB");

    }
}

-Xmx:Max_Memory = 3799515136字节3623.5MB
-Xms:Total_Memory = 257425408字节245.5MB

对JVM的内存进行调整时,必须将Max_Memory和Total_Memory调为一致,避免运行时GC和应用程序运行时争抢内存,理论值峰值和峰谷忽高忽低。

idea下的Jvm参数设置:

实现Max和Total都是1024M

Vm参数: -Xms1024m-Xmx1024m-XX:+PrintGCDetails

在这里插入图片描述
配置完再次运行上面的代码:
在这里插入图片描述
得到验证:

实现OutOfMemoryErro异常

在java虚拟机规范描述中,除了程序计数器外,虚拟机的几个运行时区域都有可能发生OutOfMemoryErro异常的可能性。

Java堆溢出

验证:

首先将JVM的内存改小使之比较容易的实现溢出:
在这里插入图片描述
然后new一个大于内存的数组:

 byte []bytes = new byte[1024 * 1024* 40];

得到异常:
在这里插入图片描述

虚拟机栈和本地方法栈的溢出

由于HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,虽然-Xoss参数存在(设置本地方法栈的大小),但是实际时无效的,栈容量只能由-Xss参数设定,关于这两块区域,Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出Stackoverflow Erro的异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutofMemory Error

在单线程下,无论是由于栈帧太大还是虚拟机容量太小,当内存无法分配的时候:虚拟机都抛出的时StackOverflow Error 异常。

验证:

public class Test {
    private int stackLength = 1;
    public void stackLeak(){
        stackLength++;
        stackLeak();

    }
    public static void main(String[] args) throws Throwable{
        Test test = new Test();
        try{
            test.stackLeak();
        }catch(Throwable e){
            System.out.println("stack length:"+ test.stackLength);
            throw e;
        }
    }
}

在这里插入图片描述
如果测试时不限於单线程,通过不断地建立线程的方式倒是可以产生内存溢出异常,但是这样的溢出异常与栈空间是否足够大并不存在任何联系.
原因:操作系统分给每个进程的内存师有限的,虚拟机提供参数来控制Java堆和方法区的这里两部分的内存的最大值。每个线程分到的栈容量越大,可以建立的线程数量自然就越少,建立线程时越容易把内存耗尽。
如果使用虚拟机默认参数,栈深度在大多数情况下达到1000~2000是没有问题的,对于常用的方法调用,这个深度完全够了,但是如果建立过多的线程导致内存溢出,只能减少最大堆和减小栈容量来换取更多的线程。

方法区和运行时常量池的溢出

在jdk 1.6之前:
运行时常量池时方法区的一部分,所以我们用
-XX:PermSize 和 -XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量
List list = new ArrayList();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
我们会得到
OutofMemoryError : PemGen space
的异常
而在1.7中不会发生异常
因为什么呢:
再写一个实例:

        String str1 = new StringBuilder("hello").append("world").toString();
        System.out.println(str1.intern() == str1);
        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);


在java1.6中会输出两个false,而在之后的1.7,1.8里会输出一个true一个false。
因为在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的是这个字符串的引用,而由StringBuilder创建的字符串实例在Java堆中,所以不是同一个引用,返回false.
在JDK1.7中intern()不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和StringBuilder创建的那个字符串是同一个。对str2比较返回false是因为这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有他的引用了,不符合首次出现这个条件,所以返回false。


方法区用于存放Class的相关消息,如类名、访问修饰符,常量池,字段描述,方法描述。
想让他溢出就要产生大量的类去填满方法区。
不再验证。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章