实战:内存分配与回收策略

文章目录

  • 实战:内存分配与回收策略
    • 对象优先在Eden分配
    • 大对象直接进入老年代
    • 长期存活的对象将进入老年代
    • 动态对象年龄判定
    • 空间分配担保

对象优先在Eden分配

试分配三个2MB大小和一个4MB大小的对象,在运行时通过-Xms20M-Xmx20M-Xmn10M这三个参数限制了Java堆大小为20MB,不可扩展,其中10MB分配给新生代,剩下的10MB分配给老年代。-XX:Survivor-Ratio=8决定了新生代中Eden区与一个Survivor区的空间比例是8∶1,从输出的结果也清晰地看到“eden space 8192K、from space 1024K、tospace 1024K”的信息,新生代总可用空间为9216KB(Eden区+1个Survivor区的总容量)

执行testAllocation()中分配allocation4对象的语句时会发生一次Minor GC,这次回收的结果是新生代7657K变为376K,而总内存占用量则几乎没有减少(因为allocation1、2、3三个对象都是存活的,虚拟机几乎没有找到可回收的对象)。产生这次垃圾收集的原因是为allocation4分配内存时,发现Eden已经被占用了6MB,剩余空间已不足以分配allocation4所需的4MB内存,因此发生Minor GC。垃圾收集期间虚拟机又发现已有的三个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。

/*** @description: 对象优先在Eden分配* @author: Mr.O* @create: 2020-11-02 23:17**/
public class EdenFirst {private static final int _1MB = 1024 * 1024;public static void main(String[] args) {testAllocation();}/*** VM参数:-verbose:gc* -Xms20M  初始堆内存* -Xmx20M  最大堆内存* -Xmn10M  新生代大小* -XX:+PrintGCDetails  打印GC日志* -XX:SurvivorRatio=8  Eden与Survivor为8:1*  -XX:+UseSerialGC Serial(Young区)+Serial Old(Old区)的收集器组合*/public static void testAllocation() {byte[] allocation1, allocation2, allocation3, allocation4;allocation1 = new byte[2 * _1MB];allocation2 = new byte[2 * _1MB];allocation3 = new byte[2 * _1MB];allocation4 = new byte[4 * _1MB]; // 出现一次Minor GC}
}
[GC (Allocation Failure) [DefNew: 7657K->376K(9216K), 0.0058498 secs] 7657K->6520K(19456K), 0.0058889 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heapdef new generation   total 9216K, used 4636K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf029150, 0x00000007bf400000)from space 1024K,  36% used [0x00000007bf500000, 0x00000007bf55e1f0, 0x00000007bf600000)to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)tenured generation   total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)the space 10240K,  60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)Metaspace       used 3058K, capacity 4496K, committed 4864K, reserved 1056768Kclass space    used 337K, capacity 388K, committed 512K, reserved 1048576K

日志个参数说明:

**Allocation Failure:**表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。

**DefNew:**表示新生代使用的是serial收集器

[DefNew: 7657K->376K(9216K), 0.0058498 secs] 7657K->6520K(19456K), 0.0058889 secs]

**7657K->376K(9216K):**表示新生代从7657K变为376K ,括号里表示新生代总大小

**0.0058498 secs:**表示GC时间

**7657K->6520K(19456K):**表示整堆容量从7657K变为6520K,后面参数同样是时间,括号里表示整堆总大小

大对象直接进入老年代

private static final int _1MB = 1024 * 1024;public static void main(String[] args) {testPretenureSizeThreshold();
}
/*** VM参数:-verbose:gc* -Xms20M 初始堆内存* -Xmx20M 最大堆内存* -Xmn10M -XX:+PrintGCDetails gc日志* -XX:SurvivorRatio=8 eden与survive比例为8:1* -XX:PretenureSizeThreshold=3145728 对象大小超过三兆直接进入老年代*/
public static void testPretenureSizeThreshold() {byte[] allocation;allocation = new byte[4 * _1MB]; //直接分配在老年代中
}

我们看到Eden空间几乎没有被使用,而老年代的10MB空间被使用了40%,也就是4MB的allocation对象直接就分配在老年代中,这是因为-XX:PretenureSizeThreshold被设置为3MB(就是3145728,这个参数不能与-Xmx之类的参数一样直接写3MB),因此超过3MB的对象都会直接在老年代进行分配。

Heapdef new generation   total 9216K, used 1694K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K,  20% used [0x00000007bec00000, 0x00000007beda79d0, 0x00000007bf400000)from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)tenured generation   total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)the space 10240K,  40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)Metaspace       used 3052K, capacity 4496K, committed 4864K, reserved 1056768Kclass space    used 336K, capacity 388K, committed 512K, reserved 1048576K

-XX:PretenureSizeThreshold参数只对Serial和ParNew两款新生代收集器有效,HotSpot的其他新生代收集器,如Parallel Scavenge并不支持这个参数。如果必须使用此参数进行调优,可考虑ParNew加CMS的收集器组合。

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

对象通常在Eden区里诞生,如果经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其==对象年龄设为1岁。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。==对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

private static final int _1MB = 1024 * 1024;public static void main(String[] args) {testTenuringThreshold();
}
/*** VM参数:-verbose:gc* -Xms20M 初始堆内存* -Xmx20M 最大堆内存* -Xmn10M -XX:+PrintGCDetails gc日志* -XX:SurvivorRatio=8 eden与survive比例为8:1* -XX:MaxTenuringThreshold=1 设置年龄超过一岁后进入老年代* -XX:+PrintTenuringDistribution参数作用:JVM 在每次新生代GC时,打印出幸存区中对象的年龄分布。**/
public static void testTenuringThreshold() {byte[] allocation1, allocation2, allocation3;allocation1 = new byte[_1MB / 4]; // 什么时候进入老年代决定于XX:MaxTenuring-Threshold设置allocation2 = new byte[4 * _1MB];allocation3 = new byte[4 * _1MB];allocation3 = null;allocation3 = new byte[4 * _1MB];
}

-XX:MaxTenuringThreshold=1时:

说明:当执行完第二个allocation3时,由于survive去空间不够,所以对象直接进入老年代,执行第一次回收,6387K->706K(9216K),表示allocation1还在新生代,执行完第三个时allocation时,第二次回收,4966K->0K(9216K)表示allocation1进入老年代

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     723304 bytes,     723304 total
: 6387K->706K(9216K), 0.0040715 secs] 6387K->4802K(19456K), 0.0041041 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:        104 bytes,        104 total
: 4966K->0K(9216K), 0.0015760 secs] 9062K->4758K(19456K), 0.0016233 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heapdef new generation   total 9216K, used 4260K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400068, 0x00000007bf500000)to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)tenured generation   total 10240K, used 4758K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)the space 10240K,  46% used [0x00000007bf600000, 0x00000007bfaa5a88, 0x00000007bfaa5c00, 0x00000007c0000000)Metaspace       used 2992K, capacity 4556K, committed 4864K, reserved 1056768Kclass space    used 319K, capacity 392K, committed 512K, reserved 1048576K

-XX:MaxTenuringThreshold=15时:

而当-XX:MaxTenuringThreshold=15时,第二次GC发生后,allocation1对象则还留在新生代Survivor空间,这时候新生代仍然有404KB被占用。

但是我这里使用1M/4还是变为0了,但是如果是1M除8就不是0,可能是由于下面动态年龄判断引起的。

[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 414664 bytes, 414664 total
: 4859K->404K(9216K), 0.0049637 secs] 4859K->4500K(19456K), 0.0049932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 15 (max 15)
- age 2: 414520 bytes, 414520 total
: 4500K->404K(9216K), 0.0008091 secs] 8596K->4500K(19456K), 0.0008305 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heapdef new generation total 9216K, used 4582K [0x029d0000, 0x033d0000, 0x033d0000)eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000)from space 1024K, 39% used [0x031d0000, 0x03235338, 0x032d0000)to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000)tenured generation total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000)the space 10240K, 40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000)compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)the space 12288K, 17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)
No shared spaces configured.

动态对象年龄判定

HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于

Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄。

将设置-XX:MaxTenuring-Threshold=15,发现运行结果中Survivor占用仍然为0%,而老年代比预期增加了6%,也就是说allocation1、allocation2

对象都直接进入了老年代,并没有等到15岁的临界年龄。因为这两个对象加起来已经到达了512KB,并且它们是同年龄的,满足同年对象达到Survivor空间一半的规则。我们只要注释掉其中一个对象的new操作,就会发现另外一个就不会晋升到老年代了。(但是我这里用JDK8做测试的时候注释掉一个还是0,然后我注释一个,将另一个改成8才不是0,有可能是我新生代中还有其他对象引起的,或者是JDK的版本有关)

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     985464 bytes,     985464 total
: 6643K->962K(9216K), 0.0039320 secs] 6643K->5058K(19456K), 0.0039559 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
: 5222K->0K(9216K), 0.0026061 secs] 9318K->4988K(19456K), 0.0026458 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heapdef new generation   total 9216K, used 4260K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)tenured generation   total 10240K, used 4988K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)the space 10240K,  48% used [0x00000007bf600000, 0x00000007bfadf1b0, 0x00000007bfadf200, 0x00000007c0000000)Metaspace       used 2993K, capacity 4556K, committed 4864K, reserved 1056768Kclass space    used 319K, capacity 392K, committed 512K, reserved 1048576K

空间分配担保

==在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次Minor GC可以确保是安全的。==如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者-XX:HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC。

JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC,否则将进行Full GC。

JVM3:实战:内存分配与回收策略相关推荐

  1. 实战:内存分配与回收策略

    实战:内存分配与回收策略 ​ Java技术体系的自动内存管理,最核心的目标就是自动化的解决两个问题:内存分配和内存回收,在之前我们讲的都是内存回收,现在我们来看一下内存分配的细节,这里我是使用JDK1 ...

  2. JVM:内存分配与回收策略?Full GC 的触发条件?StopTheWorld ?

    内存分配与回收策略 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快. Full GC: ...

  3. 【拥抱大厂系列】百度面试官问过的 “JVM内存分配与回收策略原理”,我用这篇文章搞定了

    点个赞,看一看,好习惯!本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了3个月总结的一线大厂Java面试总结,本人已拿腾 ...

  4. 深入理解Java虚拟机(第三弹)- JVM 内存分配与回收策略原理,从此告别 JVM 内存分配文盲

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:打卡活动第二期来啦,100% 能获得奖品个人原创+1博客:点击前往,查看更多 虚拟机系列文章 深入理解 Java ...

  5. Java 虚拟机内存分配与回收策略

    垃圾收集器与内存分配策略参考目录: 1.判断 Java 对象实例是否死亡 2. Java 中的四种引用 3.垃圾收集算法 4. Java9 中的 GC 调优 5.内存分配与回收策略 一.对象优先在 E ...

  6. JVM之内存分配与回收策略

    JVM之内存分配与回收策略 来源 https://www.cnblogs.com/xiaoxi/p/6557473.html JVM分代垃圾回收策略的基础概念 来源 https://www.cnblo ...

  7. JVM 之 内存分配与回收策略

    不诗意的女程序媛不是好厨师~ 转载请注明出处,From李诗雨-[https://blog.csdn.net/cjm2484836553/article/details/103842357] <J ...

  8. jvm深入理解:内存分配与回收策略(优先在Eden分配、大对象直接进入老年代、长期存活的对象将进入老年代、动态对象年龄判定、空间分配担保)

    出入:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存. 象的内存分配,从 ...

  9. JVM 优化实操 - 内存分配与回收策略

    JVM 体系难学的一个地方就是理论一大堆,却难以实践.今天就尝试着在本地实践一把,体会 JVM 内存分配和垃圾回收的过程,尽可能的能够通过本地实际操作感受一下之前学习到的一些理论知识,作为一名初学者, ...

最新文章

  1. InsightFace笔记
  2. UnityShader入门精要-3.3 UnityShader的结构
  3. JS详细入门教程(上)
  4. python无法打开文档_win32com Excel。应用程序无法打开任何文档
  5. LocalDateTime、LocalDate、Date的相互转换(亲测LocalDateTime转LocalDate)
  6. 太平洋大西洋水流问题如何解决?一文了解图在前端中的应用
  7. 一个黑色全屏的计时器_我入手了一个1000多的智能手环,值吗?|Fitbit Charge 4测评...
  8. JEECG 树列表操作总刷新列表,需要重新展开问题 【官方补丁,适用所有版本】
  9. phpcmsV9调用顶级父栏目的所有子栏目 - 调用总结
  10. 解决Tensorflow2.0出现:AttributeError: module ‘tensorflow‘ has no attribute ‘get_default_graph‘的问题
  11. 数组、链表、哈希表(数据结构)-代码随想录
  12. 干货!10分钟,用Python生成图文并茂的PDF报告!
  13. 在线编辑word文档
  14. 家用计算机的使用说明,AWIND奇机家用无线投屏器使用说明
  15. PR常见问题:pr导入素材没有音轨怎么办
  16. 装了Restorator,打开应用程序,提示不支持此接口的解决方法
  17. jle汇编_JNB, JBE, JGE, JLE 指令的转移条件 5
  18. SpringMvc导入Excel
  19. 用Python编写代码来理解赢得《英雄联盟》游戏的胜利的最重要因素
  20. 虚幻——动画蓝图、状态机制作人物走跑跳动作

热门文章

  1. hexo博客中添加categories分类
  2. 利用Redis原子计数器incr实现计数器及接口限流
  3. [Job服务] - 自定义计划任务服务
  4. 蓝桥杯泊松分酒java_蓝桥杯-泊松分酒 - steven_wjg的个人空间 - OSCHINA - 中文开源技术交流社区...
  5. Matlab通过整除判断倍数
  6. axios与拦截器的简单结合
  7. MYSQL 千万数据速度以及极限测试InnoDb--INSERT 拼接极限(一)
  8. 增强 扫描王 源码_OpenCV探索之路(二十二):制作一个类“全能扫描王”的简易扫描软件...
  9. android img 制作工具,Android 镜像文件制作 【ramdisk.img system.img userdata.img】
  10. ubuntu(linux)打开jnlp文件