深入理解Java虚拟机(3)—— 内存分配与回收策略

刺骨的言语ヽ痛彻心扉 2022-05-25 06:35 304阅读 0赞

1.内存分配与回收策略

1.1 对象优先在Eden分配

  1. 绝大部分,对象在新生代的Eden区中分配。方Eden区没有足够空间时,虚拟机发起一次MinorGC
  2. 代码演示如下:
  3. public class testAllocation {
  4. private static final int _1MB = 1024 * 1024;
  5. /**
  6. * V M 参数 : -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  7. * -XX:+UseSerialGC 设置使用Serial + Serial Old 收集器组合
  8. * -Xms20M -Xmx20M -Xmn10M 限制Java堆大小为20M,10M分配给新生代,10M分配给老年代
  9. * -XX:+PrintGCDetails 打印GC日志
  10. * -XX:SurvivorRatio=8 决定Eden区与一个Survivor区的空间比例是 8 : 1 新生代总可用9M(Eden区 + 1个Survivor区)
  11. */
  12. public static void main(String[] args) {
  13. byte[] allocation1, allocation2, allocation3, allocation4;
  14. allocation1 = new byte[2 * _1MB];
  15. allocation2 = new byte[2 * _1MB];
  16. allocation3 = new byte[2 * _1MB];
  17. allocation4 = new byte[4 * _1MB]; // 出现Minor GC
  18. }
  19. }
  20. 当为allocation4分配对象时会发生Minor GCGC导致新生代6309K变为660K,总占用量并没有减少(因为allocation1allocation2allocation3三个对象都是存活的,没有可回收的对象)。GC发生原因是新生代Eden还剩 9M - 6M - 1M= 2M,不够分配allocation4GC期间虚拟机又发现已有的32MB大小的对象无法放入Survivor空间,所以只好提前转移到老年代中。
  21. 打印日志如下
  22. [GC (Allocation Failure) [DefNew: 6309K->660K(9216K), 0.0028223 secs] 6309K->4756K(19456K), 0.0028566 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  23. Heap
  24. def new generation total 9216K, used 7042K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  25. eden space 8192K, 77% used [0x00000000fec00000, 0x00000000ff23b5f0, 0x00000000ff400000)
  26. from space 1024K, 64% used [0x00000000ff500000, 0x00000000ff5a53a8, 0x00000000ff600000)
  27. to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  28. tenured generation total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  29. the space 10240K, 40% used [0x00000000ff600000, 0x00000000ffa00020, 0x00000000ffa00200, 0x0000000100000000)
  30. Metaspace used 3496K, capacity 4498K, committed 4864K, reserved 1056768K
  31. class space used 387K, capacity 390K, committed 512K, reserved 1048576K

1.2 大对象直接进入老年代

  1. 大对象指的是占用大量连续内存的Java对象。虚拟机提供-XX:PretenureSizeThreshol参数(只对SerialParNew两款收集器有效),令大于这个设置值得对象直接在老年代分配。可以避免在Eden区和两个Survivor区之间大量的内存复制。

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

  1. 虚拟机给每个对象定义一个对象年龄计数器,如果对象在Eden区出生并经过一次Minor GC后仍然存活,并且能被Survivor所容纳的话,将会被移动到Survivor空间中,并且对象年龄设置为1。对象在Survivor区每经过一次Minor GC,年龄加1岁。当年龄到一定的程度(默认15),就会被晋升到老年代。
  2. 对象晋升老年代的年龄阈值,可以通过-XX:MaxTenuringThreshold 设置。

1.4 动态对象年龄判定

  1. 如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象直接进入老年代。不需要等到MaxTenuringThreshold
  2. public class TestTenuringThreshold {
  3. private static final Integer _1MB = 1024 * 1024;
  4. /**
  5. * V M 参数 : -verbose:gc -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  6. * -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
  7. * -XX:MaxTenuringThreshold=15 设置新生代晋升老年代阈值
  8. * -XX:+PrintTenuringDistribution 得到更详细的GC输出
  9. */
  10. public static void main(String[] args) {
  11. byte[] allocation1, allocation2, allocation3, allocation4;
  12. allocation1 = new byte[_1MB / 4];
  13. allocation2 = new byte[_1MB / 4];
  14. // allocation1 + allocation2 大于survivor空间一半
  15. allocation3 = new byte[4 * _1MB];
  16. allocation4 = new byte[4 * _1MB];
  17. allocation4 = null;
  18. allocation4 = new byte[4 * _1MB];
  19. }
  20. }

结果

  1. [GC[DefNew
  2. Desired survivor size 524288 bytes, new threshold 1 (max 15)
  3. - age 1: 1048576 bytes, 1048576 total
  4. : 6630K->1024K(9216K), 0.0032294 secs] 6630K->5213K(19456K), 0.0032646 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
  5. [GC[DefNew
  6. Desired survivor size 524288 bytes, new threshold 15 (max 15)
  7. - age 1: 136 bytes, 136 total
  8. : 5204K->0K(9216K), 0.0015459 secs] 9393K->5213K(19456K), 0.0015618 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  9. Disconnected from the target VM, address: '127.0.0.1:59811', transport: 'socket'
  10. Heap
  11. def new generation total 9216K, used 4178K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
  12. eden space 8192K, 51% used [0x00000000f9a00000, 0x00000000f9e14820, 0x00000000fa200000)
  13. from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200088, 0x00000000fa300000)
  14. to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
  15. tenured generation total 10240K, used 5213K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
  16. the space 10240K, 50% used [0x00000000fa400000, 0x00000000fa917428, 0x00000000fa917600, 0x00000000fae00000)
  17. compacting perm gen total 21248K, used 3050K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
  18. the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0fa920, 0x00000000fb0faa00, 0x00000000fc2c0000)
  19. No shared spaces configured.

1.5 空间分配担保

  1. 在发生Minor GC之前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果条件成立,Minor GC确保是安全的。如果不成立,虚拟机会查看HandlePromotionFailure设置是否允许担保,如果允许,会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象平均大小,大于,尝试进行Minor GC,存在风险;小于,或者不允许,直接进行Full GC JDK 1.6 Update 24 之后,HandlePromotionFailure不再使用。规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小进行Minor GC,否则进行Full GC

发表评论

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

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

相关阅读

    相关 Java虚拟内存分配回收策略

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