JVM 内存分配与回收策略

Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。

Full GC:发生在老年代上,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 很多。

1. 内存分配策略

1.1 对象优先在 Eden 分配

大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC

测试程序
尝试分配3个2MB大小和1个4MB大小的对象,-Xms20M -Xmx20M -Xmn10M 这3个参数限制了Java堆大小为20MB,不可拓展,其中10MB分给新生代,剩下10MB分配给老年代
-XX:SurvivorRatio=8 规定Eden:Survivor8:1,所以新生代中可用空间为9MB

public class Main {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] all1, all2, all3, all4;
        all1 = new byte[2 * _1MB];
        all2 = new byte[2 * _1MB];
        all3 = new byte[2 * _1MB];
        all4 = new byte[2 * _1MB]; // 出现一次 Minor GC
    }
}

运行参数

java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 Main

运行结果

[GC (Allocation Failure) [PSYoungGen: 6651K->368K(9216K)] 6651K->6520K(19456K), 0.0041477 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 368K->0K(9216K)] [ParOldGen: 6152K->6410K(10240K)] 6520K->6410K(19456K), [Metaspace: 2507K->2507K(1056768K)], 0.0035600 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 2212K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 27% used [0x00000000ff600000,0x00000000ff8290e0,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 6410K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 62% used [0x00000000fec00000,0x00000000ff242978,0x00000000ff600000)
 Metaspace       used 2514K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 264K, capacity 386K, committed 512K, reserved 1048576K

其中PSYoungGen新生代ParOldGen老年代Metaspace class space 属于永久代

分配all1,all2,all3新生代空间还够,当分配 all4 时新生代空间不足,发生Minor GC,GC期间虚拟机发现已有的3个2MB对象无法全部放入Survivor空间,所以通过分配担保机制转移到老年代

GC之后all4分配到Eden空间(占用2MB),老年代占用6MB,通过日志可以看到这个过程。

在这里插入图片描述

1.2 大对象直接进入老年代

大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组

经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。

-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间发生大量的内存复制。

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

为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 后依然存活的将移动到 Survivor 中,每经过这样的操作年龄就增加 1 岁,增加到一定年龄则移动到老年代中。
-XX:MaxTenuringThreshold 用来定义年龄的阈值

1.4 动态对象年龄判定

虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold 才能晋升老年代,如果在 Survivor相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。

1.5. 空间分配担保

在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。

如果不成立的话虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC(如果失败则取消Minor GC,进行Full GC,再进行Minor GC);如果小于,或者 HandlePromotionFailure 设置不允许冒险,那么就要进行一次 Full GC。

2. Full GC 的触发条件

对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:

  • 调用 System.gc()
    只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

  • 老年代空间不足
    老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。
    为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

  • 空间分配担保失败
    使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。

  • Concurrent Mode Failure
    执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。

  • JDK 1.7 及以前的永久代空间不足
    在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。
    当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError
    为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值