对象创建与堆

这一节主要介绍对象创建时,在堆中的一些过程。

回忆下,我们之前说的,什么时候会发生垃圾回收?

除了在一些安全点处也许会发生垃圾回收(只是也许),如果在所需内存不足的情况下,一定会发生垃圾回收。

分配堆空间

首先通过设置参数,把堆空间设置为 20M,其中 新生代 10M,老年代 10M。

参数设置:

-Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails

结果为:

Heap
 PSYoungGen      total 9216K, used 1685K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 20% used [0x00000007bf600000,0x00000007bf7a5580,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)

创建一个新对象

我们首先创建一个对象,这个对象占用 2M 的空间。

package heap;

public class CreateObject {
    public static void main(String[] args) {
        byte[] obj1 = new byte[1024 * 1024 * 2];
    }
}

最后的输出:

Heap
 PSYoungGen      total 9216K, used 3733K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 45% used [0x00000007bf600000,0x00000007bf9a5590,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)

可以看到,新生代 被占用了,老年代占用为 0K,没有被使用。

所以,new 的对象先放在 eden 区。

填满 eden 区

在填满 eden 区后,会发生什么呢?因为 survivor 区实在太小了,很难看到。所以,这里可以借助 Visual VM,来观察,更加直观。

程序如下:

package heap;

public class CreateObject {
    public static void main(String[] args) {
        while(true){
            byte[] bytes = new byte[1024 * 512];
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

重点是看右边的记录图。注意,这里我们将每次创建对象的大小设置为了 0.5M。

当 Eden 满的时候,会调用垃圾回收器,调用垃圾回收器后,Eden 出现了低谷,Survivor 出现了一个增长。老年区也出现了一个增长。

当 Eden 满的时候,如果 Survivor 区有足够的空间容纳存活对象,那么可以把存活对象放入 Survivor,多的对象放入老年区。

现在,我们把对象的大小调大。设置为 2M,这样 Survivor 就无法存放下。

可以看到,在经过一次垃圾回收的时候(可以看到GC Time 上有波峰,说明执行了一次垃圾回收),但我们注意到,Survivor 区中并没有被占用。说明垃圾回收过程中,直接将存活对象放到了老年代中。

再来聊聊 survivor 区

对象通常在 Eden 区里诞生,如果经过第一次 MInor GC 后仍然存活,并且能够被 Survivor 容纳的话,该对象会被移动到 Survivor 区,并且将其年龄设置为 1 岁。对象在 Survivor 区每熬过一次 Minor GC,年龄就增加 1 岁,当它年龄增大到一定程度(默认是 15 岁),就会被晋升到老年代。

特殊情况

有些时候,如果用户创建了大对象,如很长的字符串或者元素很多的数组的时候。这种大对象都占用大量的内存,像这种大对象,有很大概率是长时间使用的,不然为什么要创建大对象。

如果大对象朝生夕灭,我们知道在 Java 8 中,新生代默认采用的 标记-复制 算法,那么对于大对象而言,是非常耗时的。

所以,如果 JVM 设置了一个阈值,那么当分配的对象大于这个阈值的时候,会直接被分配到老年代。

总结

posted @ 2020-06-22 23:41  土堆碎念  阅读(301)  评论(0编辑  收藏  举报