本文主要是基于Java内存回收机制作一些测试,目的是进一步了解JVM的相关机制。

    您可能需要阅读:JAVA 内存泄露详解(原因、例子及解决),以便对Java中的内存管理有初步的认识。

    01

    总的来说,就是申请2次超过最大可用内存一半的内存,然后观察在不同情况下,会出现什么情况。

    首先,我们都知道 Java 中 int 是 4 字节的,当然你也可以尝试一下:

System.out.println(Integer.BYTES);
//输出:4

    运行时,我们可以通过如下方式获取JVM最大的内存:

Runtime.getRuntime().maxMemory()

    所以,想要一次分配超过最大内存一半的方式可以如下使用:

int[] ints1 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];

     好,直接看第一个测试:

    public static void test1(){
        int[] ints1,ints2;
        ints1 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
        ints2 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
    }

    如果至少读了文章开始部分推荐的文章,那么至少可以分析得到如下结论:

    为ints1分配内存时,肯定是没有问题的,因为有足够的内存可以使用,但是,在为ints2分配时,ints1的内存是不能被回收的,而调用test1()结果很显然:OutOfMemoryError。这个例子很简单,目的是由浅到深。要解决这里的问题,我们将代码修改为如下肯定就不会有问题了:

    public static void test2(){
        int[] ints1,ints2;
        ints1 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
        ints1 = null;
        ints2 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
    }

    因为对象被设为 null 就暗示GC可以清理这个对象了(但是GC不一定马上就会清理),然后在准备为ints2申请内存时,发现内存已经不足以功ints2使用了,所以马上启动GC开始清理工作(也可能会提前就启动了),所以,调用test2()是没有问题的。

    那么如果例子是这样的呢:

    public static void test3(){
        int[] ints1;
        ints1 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
        ints1 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
    }

    在测试得到结果之前,先分析会有更多收获:我们知道暗示GC可以回收一个对象的内存,除了将对象设置为null,另一种情况就是对象被重新赋值,即这个对象原先申请的内存的访问方式(如果有c++基础,这个就太容易理解了)就被删除了。那么上面代码中,可能就有一点不好拿捏结果了:

  1.     要为ints1重新赋值(即,将它指向另外一块内存),就应该先分配好这块内存,然后才让ints1指向这块内存,即在第二次分配前,GC并不能回收ints1原先指向的那块内存,所以结论是连续两次分配了内存,在第二次分配时会出错,即调用test()3会发生OutOfMemoryError 。
  2.     编译器很容易(相对)知道,ints1要被重新赋值了,即使应该先分配内存,再让ints1指向它,经过优化后,可以先让GC释放掉之前申请的内存,然后再发生第二次内存的申请,所以结论是调用test()不会有任何问题。

    不过,以上两点,也并没有冲突,我们直接调用,发现确实出现的是第一种情况(调用发生错误),但需要注意的是,我们平时一般都使用的是sun公司的jvm,其他jvm在没有真正测试过时,不能妄下结论(比如:IBM的jvm在调用上述test3时,就不会有任何问题)。

    当然,sun公司的jvm,在运行如下例子时是肯定不会有问题的,IBM的jvm就更不用说了,因为这并不需要编译器(或JVM)有多智能:

    public static void test4(){
        int[] ints1;
        ints1 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
        ints1 = new int[1];//或者ints1 = null
        ints1 = new int[(int) (Runtime.getRuntime().maxMemory()*0.25*0.6)];
    }

    因为Java规范中,很多都不规定具体的实现,比如GC,所以他们的处理方式也很难相同。

    而对于我们,应该了解其基本的原理,就像上面得出的两种结论,并不是单一的测试就能说明问题,而正确的分析(基于基本原理),一定是对的。