深入理解Java虚拟机:JVM高级特性与最佳实践-总结-6

news/2024/11/24 1:13:24/

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-6

    • 内存分配与回收策略
      • 动态对象年龄判定
      • 空间分配担保
    • 垃圾收集器与内存分配策略小结

内存分配与回收策略

动态对象年龄判定

为了能更好地适应不同程序的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄。

执行下面示例代码中的testTenuringThreshold2()方法,并将设置-XX:MaxTenuring-Threshold=15,发现运行结果中Survivor占用仍然为0%,而老年代比预期增加了6%,也就是说allocation1allocation2对象都直接进入了老年代,并没有等到15岁的临界年龄。因为这两个对象加起来已经到达了512KB,并且它们是同年龄的,满足同年对象达到Survivor空间一半的规则。我们只要注释掉其中一个对象的new操作,就会发现另外一个就不会晋升到老年代了。

private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15
* -XX:+PrintTenuringDistribution
*/
@SuppressWarnings("unused")
public static void testTenuringThreshold2() {byte[] allocation1, allocation2, allocation3, allocation4;allocation1 = new byte[_1MB / 4]; // allocation1+allocation2大于survivo空间一半allocation2 = new byte[_1MB / 4];allocation3 = new byte[4 * _1MB];allocation4 = new byte[4 * _1MB];allocation4 = null;allocation4 = new byte[4 * _1MB];
}

运行结果:

[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 676824 bytes, 676824 total
: 5115K->660K(9216K), 0.0050136 secs] 5115K->4756K(19456K), 0.0050443 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 15 (max 15)
: 4756K->0K(9216K), 0.0010571 secs] 8852K->4756K(19456K), 0.0011009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heapdef new generation total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000)eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000)from space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000)to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000)tenured generation total 10240K, used 4756K [0x033d0000, 0x03dd0000, 0x03dd0000)the space 10240K, 46% used [0x033d0000, 0x038753e8, 0x03875400, 0x03dd0000)compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)the space 12288K, 17% used [0x03dd0000, 0x03fe09a0, 0x03fe0a00, 0x049d0000)
No shared spaces configured.

空间分配担保

在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次Minor GC可以确保是安全的。如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者-XX:HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC。 “冒险”指的是:新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况——最极端的情况就是内存回收后新生代中所有对象都存活,需要老年代进行分配担保,把Survivor无法容纳的对象直接送入老年代。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,但一共有多少对象会在这次回收中活下来在实际完成内存回收之前是无法明确知道的,所以只能取之前每一次回收晋升到老年代对象容量的平均大小作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

取历史平均值来比较其实仍然是一种赌概率的解决办法,也就是说假如某次Minor GC存活后的对象突增,远远高于历史平均值的话,依然会导致担保失败。如果出现了担保失败,那就只好老老实实地重新发起一次Full GC,这样停顿时间就很长了。虽然担保失败时绕的圈子是最大的,但通常情况下都还是会将-XX:HandlePromotionFailure开关打开,避免Full GC过于频繁。具体见下面的代码示例,先以JDK 6 Update 24之前的HotSpot运行测试代码。

private static final int _1MB = 1024 * 1024;
/**
* VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
*/
@SuppressWarnings("unused")
public static void testHandlePromotion() {byte[] allocation1, allocation2, allocation3, allocation4, allocation5, alloca-tion6, allocation7;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];
}

以-XX:HandlePromotionFailure=false参数来运行的结果:

[GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
[GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][Tenured: 4096K->4244K(10240K), 0.0042901 secs] 10474K->4244K(19456K), [Perm : 2104K->2104K(12288K)], 0.0043613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

以-XX:HandlePromotionFailure=true参数来运行的结果:

[GC [DefNew: 6651K->148K(9216K), 0.0054913 secs] 6651K->4244K(19456K), 0.0055327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6378K->148K(9216K), 0.0006584 secs] 10474K->4244K(19456K), 0.0006857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

在JDK 6 Update 24之后,这个测试结果就有了差异,-XX:HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略,虽然源码中还定义了-XX:HandlePromotionFailure参数,但是在实际虚拟机中已经不会再使用它。JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC,否则将进行Full GC。

垃圾收集器与内存分配策略小结

介绍了垃圾收集的算法、若干款HotSpot虚拟机中提供的垃圾收集器的特点以及运作原理。通过代码实例验证了Java虚拟机中自动内存分配及回收的主要规则。
垃圾收集器在许多场景中都是影响系统停顿时间和吞吐能力的重要因素之一,虚拟机之所以提供多种不同的收集器以及大量的调节参数,就是因为只有根据实际应用需求、实现方式选择最优的收集方式才能获取最好的性能。没有固定收集器、参数组合,没有最优的调优方法,虚拟机也就没有什么必然的内存回收行为。因此学习虚拟机内存知识,如果要到实践调优阶段,必须了解每个具体收集器的行为、优势劣势、调节参数。


http://www.ppmy.cn/news/76875.html

相关文章

【每日一题Day213】LCP 33. 蓄水 | 枚举+贪心

LCP 33. 蓄水 给定 N 个无限容量且初始均空的水缸,每个水缸配有一个水桶用来打水,第 i 个水缸配备的水桶容量记作 bucket[i]。小扣有以下两种操作: 升级水桶:选择任意一个水桶,使其容量增加为 bucket[i]1蓄水&#xff…

与chatGPT聊import与export的前世今生

前言 通过与chatGPT的聊天对CommonJS、AMD 和 ES6 模块的理解更加的深入,有些问题说实话在网络上是找不到答案的,而且大多存在的错误,因为有一些问题的讨论难免出现误差,目前在网络上寻找答案基本上以个人博客的形式存在&#xf…

Markdown常用语法技巧

一些常用特殊符号 ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ ❶❷❸❹❺❻❼❽❾❿⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴ ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ 参考 符号大全 http://www.fhdq.net/ ① 标题 使用#号标记,可以表示1-6级标题, 随#的…

记录--使用率比较低的10个Web API

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 avaScript中有些API可能使用率比较低,下面我们逐一介绍它们的用法和使用场景。 至于标题,主要是想让你进来看看,兄弟们别打我! Blob API Blob API 用于处…

如何对项目进度进行跟踪?逐步完善项目计划

我接手了一个小项目,但是无论是我还是领导,都认为这是个简单的项目,最多一月时间就能搞定。但是,随着时间推移,三个月也没有将内容完善。于是我进行了反思总结,我认为存在如下问题: 1、资源协…

面了一个00后测试工程师,问啥啥不会开口就要15k,我也是麻了····

在深圳这家金融公司也待了几年,被别人面试过也面试过别人,大大小小的事情也见识不少,今天又是团面的一天, 一百多个人都聚集在一起,因为公司最近在谈项目出来面试就2个人,无奈又被叫到面试房间。 整个过程我…

如何落地中台架构

大家好,我是易安!今天我分享下如何落地中台架构。 前台和后台 讲中台之前,我们先来理解下前台和后台,这样,你才能更清楚中台的定位。 前台 比较好理解,指的是 面向C端的应用,比如像微信、淘宝这…

4、Ray对象存储和数据共享

4、Ray对象存储和数据共享 导航 1.简介和背景 2.Ray的基本概念和核心组件 3.分布式任务调度和依赖管理 4.对象存储和数据共享 5.Actor模型和并发编程 6.Ray的高级功能和扩展性 7.使用Ray构建分布式应用程序的案例研究 8.Ray社区和资源 9.核心框架介绍 10.扩展1&