JVM:内存分配策略

小咪咪 2022-05-23 05:16 367阅读 0赞

1、对象优先在Eden分配

大多情况,对象在新生代Eden区分配。当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC。虚拟机提供了参数 -XX:+PrintGCDetails ,在虚拟机发生垃圾收集行为时打印内存回收日志。

新生代Minor GC 事例

定义了4个字节数组对象,3个2MB大小、1个4MB大小,

通过-Xms20M -Xmx20M -Xmn10M 三个参数限制了Java堆大小为 20M ,不可扩展,其中的 10MB 分配给新生代,剩下 10MB 分配给老年代

-XX:SurvivorRatio=8 新生代 Eden 与 Survivor 区空间比例是 8:1:1

  1. package com.lkf.jvm;
  2. public class MinorGCDemo {
  3. private static final int _1MB = 1024 * 1024;
  4. /**
  5. * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  6. * */
  7. public static void testAllocation() {
  8. byte[] allocation1, allocation2, allocation3, allocation4;
  9. allocation1 = new byte[2 * _1MB];
  10. allocation2 = new byte[2 * _1MB];
  11. allocation3 = new byte[2 * _1MB];
  12. allocation4 = new byte[4 * _1MB]; /出现一次 Minor GC
  13. }
  14. public static void main(String[] args) {
  15. testAllocation();
  16. }
  17. }

结果:

  1. [GC (Allocation Failure) Disconnected from the target VM, address: '127.0.0.1:61454', transport: 'socket'
  2. [PSYoungGen: 6570K->704K(9216K)] 6570K->4808K(19456K), 0.0036571 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  3. Heap
  4. PSYoungGen total 9216K, used 7253K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  5. eden space 8192K, 79% used [0x00000007bf600000,0x00000007bfc657a0,0x00000007bfe00000)
  6. from space 1024K, 68% used [0x00000007bfe00000,0x00000007bfeb0000,0x00000007bff00000)
  7. to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  8. ParOldGen total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  9. object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)
  10. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  11. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

从输出结果可以清晰看到 “eden space 8192K、from space 1024K、to space 1024K”
新生代总可用空间为 9216KB (Eden区空间大小 + 1个Survivor区的总容量)

这次GC发生的原因是给allocation4对象分配内存的时候,发现Eden区已经被占用了6MB,剩余空间已经不足以分配4MB的内存,因此发生了MinorGC。GC期间有发现已有的3个2MB大小的对象已经无法全部放入Survivor空间(只有1MB大小),所以只好通过分配担保机制提前将这三个对象转移到老年代去了。

2、大对象直接进入老年代

所谓大对象是指,需要大量连续内存空间的Java对象,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来为大对象分配内存。

虚拟机提供了一个-XX:PretenureSizeThreshold 参数,让大于该值得对象直接进入老年代。这样做的目的是避免在新生代Eden区及两个Survivor区之间发生大量的内存复制。

PretenureSieThreshold 参数只对 Serial 和 ParNew 两款收集器有效,Parallel Scavenge 收集器不识别这个参数,并且该收集器一般不需要设置。如果必须使用此参数的场合,可以考虑ParNew加CMS的收集器组合。

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

-XX:+PrintCommandLineFlags 可查看默认设置收集器类型

-XX:+PrintGCDetails 打印的GC日志

  1. package com.lkf.jvm;
  2. public class PretenureSizeThresholdDemo {
  3. private static final int _1MB = 1024 * 1024;
  4. /**
  5. * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  6. * -XX:+PrintCommandLineFlags -XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC
  7. * 因为使用的是jdk1.8,所以此处特指定了使用垃圾收集器Serial
  8. * 大于3M的对象直接进入老年代
  9. */
  10. public static void testPretenureSizeThreshold() {
  11. byte[] allocation;
  12. allocation = new byte[4 * _1MB];//直接分配在老年代
  13. }
  14. public static void main(String[] args) {
  15. testPretenureSizeThreshold();
  16. }
  17. }

结果:

  1. Heap
  2. def new generation total 9216K, used 2643K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  3. eden space 8192K, 32% used [0x00000007bec00000, 0x00000007bee94ee8, 0x00000007bf400000)
  4. from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  5. to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
  6. tenured generation total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  7. the space 10240K, 40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
  8. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  9. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

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

虚拟机使用了分代收集的思想来管理内存,内存回收时为了区分哪些对象应放在新生代,哪些应该放在老年代,虚拟机为每个对象定义了一个对象年龄(Age)计数器。

如果对象被分配在Eden区并经过第一次Minor GC 后仍然存活,并且能被Survivor容乃的情况下,将被移动到Survivor中,对象年龄设为1。在Survivor区每经过一次Minor GC,年龄就加1,当对象的年龄到达一定程度时(默认15岁),就会晋升到老年代。对象晋升到老年代的阈值,可以通过参数:-XX:MaxTenuringThreshold 设置。

  1. package com.lkf.jvm;
  2. /**
  3. * 长期存活对象将进入老年代
  4. */
  5. public class MaxTenuringThresholdDemo {
  6. private static final int _1MB = 1024 * 1024;
  7. /**
  8. * VM 参 数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
  9. * */
  10. public static void testTenuringThreshold() {
  11. byte[] allocation1, allocation2, allocation3;
  12. allocation1 = new byte[_1MB / 4];
  13. //什么时候进入老年代取决于XX:MaxTenuringThreshold设置
  14. allocation2 = new byte[4 * _1MB];
  15. allocation3 = new byte[4 * _1MB];
  16. allocation3 = null;
  17. allocation3 = new byte[4 * _1MB];
  18. }
  19. public static void main(String[] args) {
  20. testTenuringThreshold();
  21. }
  22. }

-XX:MaxTenuringThreshold=1 (jdk1.8)运行结果:

  1. Heap
  2. PSYoungGen total 9216K, used 6994K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  3. eden space 8192K, 85% used [0x00000007bf600000,0x00000007bfcd4b68,0x00000007bfe00000)
  4. from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  5. to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  6. ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  7. object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)
  8. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  9. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

-XX:MaxTenuringThreshold=15 (jdk1.8)运行结果:

  1. Heap
  2. PSYoungGen total 9216K, used 6994K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  3. eden space 8192K, 85% used [0x00000007bf600000,0x00000007bfcd4b68,0x00000007bfe00000)
  4. from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  5. to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  6. ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  7. object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)
  8. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  9. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

4、动态对象年龄判定

虚拟机并不是永远要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果Survivor空间中,相同年龄对象的大小之和大于Survivor空间大小的一半,就可以直接进入老年代。

  1. package com.lkf.jvm;
  2. /**
  3. * 动态对象年龄判定
  4. */
  5. public class TenuringThresholdDemo {
  6. private static final int _1MB = 1024 * 1024;
  7. /**
  8. * VM 参 数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution -XX:+UseSerialGC
  9. * */
  10. public static void testTenuringThreshold() {
  11. byte[] allocation1, allocation2, allocation3, allocation4;
  12. allocation1 = new byte[_1MB / 4];
  13. allocation2 = new byte[_1MB / 4];
  14. //allocation1+allocation2大于Survivo空间的一半
  15. allocation3 = new byte[4 * _1MB];
  16. allocation4 = new byte[4 * _1MB];
  17. allocation4 = null;
  18. allocation4 = new byte[4 * _1MB];
  19. }
  20. public static void main(String[] args) {
  21. testTenuringThreshold();
  22. }
  23. }

输出结果:

  1. [GC (Allocation Failure) [DefNew
  2. Desired survivor size 524288 bytes, new threshold 1 (max 15)
  3. - age 1: 1048568 bytes, 1048568 total
  4. : 7087K->1023K(9216K), 0.0048599 secs] 7087K->5179K(19456K), 0.0048867 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
  5. [GC (Allocation Failure) [DefNew
  6. Desired survivor size 524288 bytes, new threshold 15 (max 15)
  7. : 5283K->0K(9216K), 0.0018311 secs] 9439K->5148K(19456K), 0.0018528 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  8. Heap
  9. def new generation total 9216K, used 4260K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  10. eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)
  11. from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  12. to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
  13. tenured generation total 10240K, used 5148K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  14. the space 10240K, 50% used [0x00000007bf600000, 0x00000007bfb07198, 0x00000007bfb07200, 0x00000007c0000000)
  15. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  16. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

5、空间分配担保

在发生Minor GC 之前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象大小总和,如果条件成立,那么Minor GC可以确保是安全的。如果不成立,虚拟机会查看HandlePromotionFailure设置的值是否允许担保失败。如果允许,那么虚拟机会检查老年代最大可用连续空间是否大于历次晋升到老年代对象大小的平均值,如果大于,将会尝试进行一次Minor GC;如果小于,或者HandlePromotionFailure设置不允许冒险,这时会进行一次Full GC。

JDK 6 Update 24 之后,HandlePromotionFailure参数不会再影响到虚拟机空间分配担保的策略,规则变为只要老年代的连续空间大于新生代对象总大小或者大于历次晋升对象大小的平均值就会进行Minor GC ,否则将进行Full GC。

Minor GC 和 Full GC的区别

  1. 新生代GCMinor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都有朝生夕死的特性,所以Minor GC 很频繁,一般回收速度也很快。
  2. 老年代GCMajor GC/Full GC):指发生在老年代的GC,出现了Major GC ,经常会伴随至少一次的Minor GCMajor GC 的速度一般会比Minor GC 10

发表评论

表情:
评论列表 (有 0 条评论,367人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JVM-内存分配与回收策略

        JAVA技术体系中的自动内存管理实际上就是自动化的解决了给对象分配内存以及回收给对象分配的内存这两个问题。回收部分通过之前的[《GC设计思路分析》][GC]和[《垃圾收

    相关 JVM中的内存分配策略

    对象的内存分配,从大方向上将,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按

    相关 jvm 垃圾回收 内存分配策略

    1.那些内存需要回收 其中程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭,不需要过多考虑回收。因此内存回收主要是在java堆和方法区。 2.对象何时回收?

    相关 JVM内存分配策略

    1、对象优先在Eden分配 大多情况,对象在新生代Eden区分配。当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC。虚拟机提供了参数 -XX:+Pri