【JVM原理】内存分配和回收策略

前言

Github:https://github.com/yihonglei/jdk-source-code-reading(java-jvm)

JVM内存结构

JVM类加载机制

JVM内存溢出分析

HotSpot对象创建、内存、访问

如何判定对象可以回收

垃圾收集算法

垃圾收集器

内存分配和回收策略

一 内存分配与回收策略概述

对象的内存分配往大方向讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型

并间接地栈上分配),对象主要分配在新生代的 Eden 区上,如果启用了本地线程分配缓冲,

将按线程优先在 TLAB 上分配。少数情况下也可能直接分配在老年代中,分配的规则并不是

百分百固定的,分配细节取决于垃圾收集器组合,还有虚拟机中与内存相关的参数设置。

Virtual:为动态收缩区域,当垃圾回收获得高吞吐量或低停顿后,会尽量缩小区域内存,

以获得更好的空间局部性。 以下内容实例分析基于 JVM Server 模式下验证。

二 对象优先在 Eden 区分配

对象通常在新生代 Eden 区中分配,当 Eden 区没有足够空间分配时,虚拟机将发生一次

Minor GC。与 Minor GC 对应的是 Major GC、Full GC。

新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备

朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现 Full GC,经常会伴随至

少一次的 Minor GC(但非绝对的,在 Parallel Scavenge 收集器的可以配置 Full GC 收集策略)。

Full GC 的速度一般会比 Minor GC 慢10 倍以上。Major GC 和 Full GC 概念上目前没有明确

定义,并不是简单的 Major GC 负责清理老年代,Full GC 清理堆空间新生代和老年代,

可以参考这里的解释,从 GC 停顿做出了解释。

Minor GC vs Major GC vs Full GC - DZone Java,如果访问不了,翻下墙。

1、eg1

相关参数:

-verbose:gc 输出显示虚拟机运行信息;

-XX:+PrintGCDetails 打印内存回收日志;

-Xmx20M -Xms20M -Xmn10M 限制堆大小为 20M,不可以扩展,10M 分配给新生代,

剩下 10 分配给老年代;

-XX:SurvivorRatio=8 配置 Eden 区与一个 Survivor 区的比例,这里是默认的 8:1,

不用显示配置也可以;

package com.jpeony.jvm.gc;

/**
 * 【优先分配在 eden 区】
 *
 * JVM 参数配置:
 * -verbose:gc -XX:+PrintGCDetails -Xmx20M -Xms20M -Xmn10M -XX:SurvivorRatio=8
 *
 * JVM 参数说明:
 * -verbose:gc 输出显示虚拟机运行信息;
 * -XX:+PrintGCDetails 打印内存回收日志;
 * -Xmx20M -Xms20M -Xmn10M 限制堆大小为 20M,不可以扩展,10M 分配给新生代,剩下 10M 分配给老年代;
 * -XX:SurvivorRatio=8 配置 Eden 区与一个 Survivor 区的比例,这里是默认的 8:1,不用显示配置也可以;
 *
 * @author yihonglei
 */
public class AllocationTest01 {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation = new byte[4 * _1MB];
    }
}

运行结果:

Heap
 PSYoungGen      total 9216K, used 6414K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 78% used [0x00000000ff600000,0x00000000ffc43b58,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 Metaspace       used 3235K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 353K, capacity 388K, committed 512K, reserved 1048576K

从 GC 日志可以看到,新生代 eden 区占用 78%,from、to 两个 Survivor 区和 ParOldGen

老年代都没有使用,验证了对象优先在 Eden 区分配的事实。

按理说,eden 区分配只是 4M,应该占 50% 才对,事实分配后占用的比实际分配的要多,

主要是因为 Java 对象并不是一个人在战斗,其它部分也占用了内存。

2、eg2

把上面代码实现修改如下:

package com.jpeony.jvm.gc;

/**
 * 【分配的内存 >=Eden 大小的一半,就直接放入了老年代】
 *
 * JVM 参数配置:
 * -verbose:gc -XX:+PrintGCDetails -Xmx20M -Xms20M -Xmn10M -XX:SurvivorRatio=8
 *
 * JVM 参数说明:
 * -verbose:gc 输出显示虚拟机运行信息;
 * -XX:+PrintGCDetails 打印内存回收日志;
 * -Xmx20M -Xms20M -Xmn10M 限制堆大小为 20M,不可以扩展,10M 分配给新生代,剩下 10M 分配给老年代;
 * -XX:SurvivorRatio=8 配置 Eden 区与一个 Survivor 区的比例,这里是默认的8:1,不用显示配置也可以;
 *
 * @author yihonglei
 */
public class AllocationTest02 {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1 = new byte[2 * _1MB];
        byte[] allocation2 = new byte[2 * _1MB];
        byte[] allocation3 = new byte[5 * _1MB];
    }
}

运行结果:

Heap
 PSYoungGen      total 9216K, used 6579K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 80% used [0x00000000ff600000,0x00000000ffc6ccb8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 5120K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 50% used [0x00000000fec00000,0x00000000ff100010,0x00000000ff600000)
 Metaspace       used 3333K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

程序执行后,eden 区用了 80%,from 未占用,to 未占用,而 ParOldGen 老年代占用 50%。

当 allocation1,allocation2 分配的时候,eden 区占用 80%,咱们分配 4M,实际占用了 6.4M,

这个时候再分配 allocation3 是 5M,eden 区剩余内存不够 5M,然后 allocation3 直接分配在

老年代,因为分配的内存 >=Eden 大小的一半,就直接放入了老年代。

三 大对象直接进入老年代

大对象是指需要大量连续内存空间的 Java 对象,最经典的对象就是那种很长的字符串以

及数组。大对象对虚拟机内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有

不少空间时就提前触发垃圾收集以获取足够的连续空间来存储大对象。

虚拟机提供了 -XX:PretenureSizeThreadshold 参数来设置大对象的阈值,超过阈值的对象

直接分配到老年代。这样做的目的是为了避免在 Eden 区和两个 Survivor 区之间发生大量的

内存复制。

1、eg1

相关参数:

-verbose:gc 输出显示虚拟机运行信息;

-XX:+PrintGCDetails 打印内存回收日志;

-Xmx20M -Xms20M -Xmn10M 限制堆大小为 20M,不可以扩展,10M 分配给新生代,

剩下 10M 分配给老年代;

-XX:SurvivorRatio=8 配置 Eden 区与一个 Survivor 区的比例,这里是默认的 8:1,不用显示

配置也可以;

-XX:PretenureSizeThreshold=3145728 (3M)设置大对象的阀值,大于该值,直接进入老年代;

-XX:+UseSerialGC 指定收集器为 Serial;

注意:PretenureSizeThreshold 参数只对 Serial 和 ParNew 两款收集器有效,我这里用的

是 Jdk8,默认使用 Parallel Scavenge,一般不需要设置,如果非得需要,可以考虑 ParNew

和 CMS 收集器组合。

package com.jpeony.jvm.gc;

/**
 * JVM 参数配置:
 * -verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC
 *
 * JVM 参数说明:
 * -verbose:gc 输出显示虚拟机运行信息;
 * -XX:+PrintGCDetails 打印内存回收日志;
 * -Xms20M -Xmx20M -Xmn10M 限制堆大小为 20M,不可以扩展,10M 分配给新生代,剩下 10M 分配给老年代;
 * -XX:SurvivorRatio=8 配置 Eden 区与一个 Survivor 区的比例,这里是默认的 8:1,不用显示配置也可以;
 * -XX:PretenureSizeThreshold=3145728 (3M)设置大对象的阀值,大于该值,直接进入老年代;
 * -XX:+UseSerialGC 指定收集器为 Serial
 * 注意:PretenureSizeThreshold 参数只对 Serial 和 ParNew 两款收集器有效,我这里用的是 Jdk8,
 * 默认使用 Parallel Scavenge,一般不需要设置,如果非得需要,可以考虑 ParNew 和 CMS 收集器组合。
 *
 * 程序说明:
 * 从结果可以看出 eden 区没怎么使用,from、to 两个 Survivor 去未使用,老年代用了40%。
 * 因为分配对象为 4M,大于设置的 3M 阀值,直接在老年代进行分配。
 *
 * @author yihonglei
 */
public class PretenureSizeThreshold {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation = new byte[4 * _1MB];
    }
}

运行结果:

Heap

 def new generation   total 9216K, used 2319K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)

  eden space 8192K,  28% used [0x00000000fec00000, 0x00000000fee43d48, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)

 Metaspace       used 3235K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 353K, capacity 388K, committed 512K, reserved 1048576K

从结果可以看出 eden 区没怎么使用,from、to 两个 Survivor 区未使用,老年代用了 40%。

因为分配对象为 4M,大于设置的 3M 阀值,直接在老年代进行分配。

四 长期存活的对象将进入老年代

    虚拟机为每一个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 区出生经过

第一次 Minor GC 后仍然存活,并能被 Survivor 容纳的话,将被移动到 Survivor 空间中,

并且对象年龄设置为 1。对存活在 Survivor 中的对象,每"熬过"一次 Minor GC,年龄就增加 1,

当它的年龄增加到一定年龄阀值(默认 15 岁),就将会被晋升到老年代中。对老年代年龄

阀值可以通过 -XX:MaxTenuringThreshold 设置。

相关参数:

-verbose:gc 输出显示虚拟机运行信息;

-XX:+PrintGCDetails 打印内存回收日志;

-Xms20M -Xmx20M -Xmn10M 限制堆大小为 20M,不可以扩展,10M 分配给新生代,

剩下 10M 分配给老年代;

-XX:SurvivorRatio=8 配置Eden区与一个 Survivor 区的比例,这里是默认的 8:1,不用显示

配置也可以;

-XX:MaxTenuringThreshold=1 年龄阀值设置,默认为 15 岁。

package com.jpeony.jvm.gc;

/**
 * JVM 参数配置:
 * -verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
 *
 * JVM 参数说明:
 * -verbose:gc 输出显示虚拟机运行信息;
 * -XX:+PrintGCDetails 打印内存回收日志;
 * -Xms20M -Xmx20M -Xmn10M 限制堆大小为20M,不可以扩展,10M 分配给新生代,剩下 10M 分配给老年代;
 * -XX:SurvivorRatio=8 配置Eden区与一个Survivor区的比例,这里是默认的8:1,不用显示配置也可以;
 * -XX:MaxTenuringThreshold=1 年龄阀值设置,默认为15岁。
 *
 * 程序说明:
 * 从执行结果可以看出 eden 使用 51%,from、to 两个 Survivor 区未使用,老年代使用 50%。
 * 按照程序内存分配,从上到下分析,allocation1 分配 _1MB/4 进入 eden 区,allocation2 分配 4MB 进入 eden 区,
 * 当第一次分配 allocation3 时,发现 eden 区内存不够,直接触发 Minor GC,allocation1 和 allocation2 按理说都进入
 * Survivor区,但是 Survivor 区只有 1MB,只能容得下 allocation1,allocation1 进入 Survivor 区并且年龄为 1,
 * 将在下一次 GC 时晋升到老年代,而 allocation2 通过担保机制直接进入老年代,allocation3 分配 4MB 则在 eden 区。
 * 当第二次分配 allocation3 时,分配 4M,这样 eden 区不够,因为上一次分配 allocation3 是 4MB,要比实际大,
 * 现在又来 4M,eden 区只有 8MB,当然 eden 区内存就不够了,这个时候又触发了一次 Minor GC,allocation1 年龄加 1,
 * 晋升到老年代,allocation2 也还在老年代,上一次的 allocation3 因为被设置为 null,直接被清除,eden 区变为 8MB 内存,
 * 第二次的 allocation3 被分配到 eden 区。
 * 最终结果就是:allocation1, allocation2 分配在老年区,allocation3 分配在新生代的 eden 区。
 *
 * @author yihonglei
 */
public class TenuringThresholdTest01 {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[_1MB / 4];
        allocation2 = new byte[4 * _1MB];
        allocation3 = new byte[4 * _1MB];

        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
    }
}

结果:

Heap
 def new generation   total 9216K, used 4150K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  50% used [0x00000000fec00000, 0x00000000ff00dbf8, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 5140K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  50% used [0x00000000ff600000, 0x00000000ffb05210, 0x00000000ffb05400, 0x0000000100000000)
 Metaspace       used 3345K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

从执行结果可以看出 eden 使用 50%,from、to 两个 Survivor 区未使用,老年代使用 50%。

按照程序内存分配,从上到下分析,allocation1 分配 _1MB/4 进入 eden 区,allocation2 分配

4MB 进入 eden 区,当第一次分配 allocation3 时,发现eden区内存不够,直接触发 Minor GC,

allocation1 和 allocation2 按理说都进入 Survivor 区,但是 Survivo r区只有 1MB,只能容得

下 allocation1,allocation1 进入 Survivor 区并且年龄为1,将在下一次 GC 时晋升到老年代,

而 allocation2 通过担保机制直接进入老年代,allocation3 分配 4MB 则在 eden 区。

当第二次分配 allocation3 时,分配 4M,这样 eden 区不够,因为上一次分配 allocation3

是 4MB,要比实际大,现在又来 4M,eden 区只有 8MB,当然 eden 区内存就不够了,

这个时候又触发了一次 Minor GC,allocation1 年龄加 1,晋升到老年代,allocation2 也还在

老年代,上一次的 allocation3 因为被设置为 null,直接被清除,eden 区变为 8MB 内存,

第二次的 allocation3 被分配到 eden区。

最终结果就是: allocation1, allocation2 分配在老年区,allocation3 分配在新生代的 eden 区。

五 动态对象年龄判断

    对象的年龄到达了 MaxTenuringThreshold 可以进入老年代,同时,如果在 survivor 区中

相同年龄所有对象大小的总和大于 survivor 区的一半,年龄大于等于该年龄的对象就可以

直接进入老年代。无需等到 MaxTenuringThreshold 中要求的年龄。

相关参数:

-verbose:gc 输出显示虚拟机运行信息;

-XX:+PrintGCDetails 打印内存回收日志;

-Xms20M -Xmx20M -Xmn10M 限制堆大小为 20M,不可以扩展,10M 分配给新生代,

剩下 10 分配给老年代;

-XX:SurvivorRatio=8 配置 Eden 区与一个 Survivor 区的比例,这里是默认的 8:1,

不用显示配置也可以;

-XX:MaxTenuringThreshold=15 年龄阀值设置,默认为 15 岁。

package com.jpeony.jvm.gc;

/**
 * JVM 参数配置:
 * -verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
 *
 * JVM 参数说明:
 * -verbose:gc 输出显示虚拟机运行信息;
 * -XX:+PrintGCDetails 打印内存回收日志;
 * -Xms20M -Xmx20M -Xmn10M 限制堆大小为20M,不可以扩展,10M分配给新生代,剩下10分配给老年代;
 * -XX:SurvivorRatio=8 配置Eden区与一个Survivor区的比例,这里是默认的8:1,不用显示配置也可以;
 * -XX:MaxTenuringThreshold=1 年龄阀值设置,默认为15岁。
 *
 * 程序说明:
 * 从结果可以看出 eden 使用 51%,from、to 两个 Survivor 未使用,老年代使用了 52%。
 * allocation1、allocation2、allocation3 分配进入 Eden 区。
 * 当第一次给 allocation4 分配内存时,eden 区内存不够,发生一次 Minor GC,
 * 此时 allocation1、allocation2 将会进入 survivor 区,而 allocation3 通过担保机制将会进入老年代。
 * 第二次发生在给 allocation4 分配内存时,此时,survivor 区的 allocation1、allocation2 达到了 survivor 区容量的一半,
 * 将会进入老年代,此次 GC 可以清理出 allocation4 原来的 4MB 空间,并将 allocation4 分配在 Eden 区。
 * 最终,allocation1、allocation2、allocation3 在老年代,allocation4 在 Eden 区。
 *
 * @author yihonglei
 */
public class TenuringThresholdTest02 {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[_1MB / 4];
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation4 = new byte[4 * _1MB];
        allocation4 = null;
        allocation4 = new byte[4 * _1MB];
    }
}

结果:

Heap
 def new generation   total 9216K, used 4235K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff022828, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400618, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 5371K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  52% used [0x00000000ff600000, 0x00000000ffb3ec10, 0x00000000ffb3ee00, 0x0000000100000000)
 Metaspace       used 3339K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 360K, capacity 388K, committed 512K, reserved 1048576K

从结果可以看出 eden 使用 51%,from、to 两个 Survivor 未使用,老年代使用了 52%。

allocation1、allocation2、allocation3 分配进入 Eden 区。

当第一次给 allocation4 分配内存时,eden 区内存不够,发生一次 Minor GC,

此时 allocation1、allocation2 将会进入 survivor 区,而 allocation3 通过担保机制将会进入

老年代。第二次发生在给 allocation4 分配内存时,此时,survivor 区的 allocation1、allocation2

达到了 survivor 区容量的一半,将会进入老年代,此次 GC 可以清理出 allocation4 原来的 4MB

空间,并将 allocation4 分配在 Eden 区。最终,allocation1、allocation2、allocation3 在老年代,

allocation4 在 Eden 区。

六 空间分配担保

    在发生 Minor GC 时,虚拟机会检查老年代连续的空闲区域是否大于新生代所有对象的总和,

若成立,则说明 Minor GC 是安全的,否则,虚拟机需要查看 HandlePromotionFailure 的值,

看是否运行担保失败,若允许,则虚拟机继续检查老年代最大可用的连续空间是否大于历次

晋升到老年代对象的平均大小,若大于,将尝试进行一次 Minor GC;

若小于或者 HandlePromotionFailure 设置不运行冒险,那么此时将改成一次 Full GC,

以上是 JDK Update 24 之前的策略,之后的策略改变了,只要老年代的连续空间大于新生代

对象总大小或者历次晋升的平均大小就会进行 Minor GC,否则将进行 Full GC。冒险是

指经过一次 Minor GC 后有大量对象存活,而新生代的 survivor 区很小,放不下这些大量

存活的对象,所以需要老年代进行分配担保,把 survivor 区无法容纳的对象直接进入老年代。

package com.jpeony.jvm.gc;

/**
 * JVM 参数配置:
 * -verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+HandlePromotionFailure
 *
 * 程序说明:
 * 一开始 allocation1、allocation2、allocation3 分配在 eden 区,同时 allocation1 被置为无效。
 * 当第一次给 allocation4 分配内存时,eden 区内存不够,发生一次 Minor GC,
 * 由于老年代的连续可用空间大于存活的对象总和,所以 allocation2、allocation3 将会进入老年代,
 * allocation1 的空间将被回收,此时整个 eden 区被清空,又变为 8MB,有空间让 allocation4 分配在 eden 区;
 * 接下来 allocation5、allocation6 接着往 eden 区分配,同时 allocation4、allocation5、allocation6 被置为无效。
 * 当第二次给 allocation7 分配内存时,eden 区内存不够,发生一次 Minor GC,allocation4、allocation5、
 * allocation6 所占的内存全部回收,把整个 eden 区清空,变为 8MB,然后将 allocation7 分配在新生代 eden 区。
 * 最后,allocation2、allocation3 在老年代,allocation7 在新生代。
 *
 * @author yihonglei
 */
public class HandlePromotion {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4,
                allocation5, allocation6, allocation7, allocation8;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation1 = null;
        allocation4 = new byte[2 * _1MB];
        allocation5 = new byte[2 * _1MB];
        allocation6 = new byte[2 * _1MB];
        allocation4 = null;
        allocation5 = null;
        allocation6 = null;
        allocation7 = new byte[2 * _1MB];
    }
}

运行结果:

Heap
 def new generation   total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4611K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  45% used [0x00000000ff600000, 0x00000000ffa80d58, 0x00000000ffa80e00, 0x0000000100000000)
 Metaspace       used 2568K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

一开始 allocation1、allocation2、allocation3 分配在 eden 区,同时 allocation1 被置为无效。

当第一次给 allocation4 分配内存时,eden 区内存不够,发生一次 Minor GC,

由于老年代的连续可用空间大于存活的对象总和,所以 allocation2、allocation3 将会进入老年代,

allocation1 的空间将被回收,此时整个 eden 区被清空,又变为 8MB,有空间让 allocation4

分配在 eden 区;

接下来 allocation5、allocation6 接着往 eden 区分配,同时 allocation4、allocation5、allocation6

被置为无效。当第二次给 allocation7 分配内存时,eden 区内存不够,发生一次 Minor GC,

allocation4、allocation5、allocation6 所占的内存全部回收,把整个 eden 区清空,变为 8MB,

然后将 allocation7 分配在新生代 eden 区。最后,allocation2、allocation3 在老年代,

allocation7 在新生代。

参考文献

《深入理解Java虚拟机》 (第二版) 周志明 著;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值