内存分配与回收策略

对象优先在Eden分配

大多数情况下,对象在新生代Eden中分配,当Eden区没有足够的空间按进行分配时,虚拟机将会引发一次Minor GC 。

在运行时通过-Xms20M、-Xmx20M、-Xmn10M这三个参数限制了Java 堆大小为20MB,不可扩展,其中10MB分配给新生代,剩下的10MB分配给老年代。-XX:SurvivorRatio-8决定了新生代中Eden区与一个Survivor区的空间比例是8:1

案例:

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

大对象直接进入老年代

大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组

缺点:

比遇到一个大对象更加坏的消息就是遇到一群"朝生夕灭"的"短命大对象",我们写程序的时候应注意避免。在 Java虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复制对象时,大对象就意味着高额的内存复制开销。

解决方案:

HotSpot虚拟机提供了-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作。

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

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

HotSpot虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活对象应当放在新生代,哪些存活对象放在老年代中。为做到这点,虚拟机给每个对象定义了一个对象年龄(age)计数器,存储在对象头中。对象通常在Eden 区里诞生,如果经过第一次MinorGC后仍然存活,并且能被Survivor 容纳的话,该对象会被移动到 Survivor 空间中,并且将其对象年龄设为1岁。对象在Survivor区中每熬过一次Minor Gc,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold 设置。

动态对象年龄判断

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

空间分配担保

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

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

相关推荐
阿猿先森4 分钟前
PyQt6+pyqtgraph折线图绘制显示
开发语言·python
legend_jz8 分钟前
【Linux】线程的互斥和同步
linux·运维·服务器·开发语言·笔记·学习·学习方法
Gungnirss19 分钟前
前后端分离,后端拦截器无法获得前端请求的token
java·前端·token
codeMaster__hyd19 分钟前
使用IDEA构建springboot项目+整合Mybatis
java·linux·centos·intellij-idea·intellij idea·1024程序员节
l1384942745119 分钟前
Java综合练习
java·开发语言·算法
人机emmo_490372112231 分钟前
第4关 Java分支结构之Switch【Java 分支结构之 Switch:灵活的选择利器】
java·开发语言·python
重生之我是数学王子37 分钟前
QT 实现仿制 网络调试器(未实现连接唯一性) QT5.12.3环境 C++实现
开发语言·c++·qt
枫の准大一1 小时前
C++从零到满绩——类和对象(中)
开发语言·c++
HEX9CF1 小时前
【数字图像处理+MATLAB】通过 Roberts, Prewitt, Sobel, LoG 等算子实现图像边缘检测:使用 edge 函数
开发语言·matlab·edge
凡人的AI工具箱1 小时前
40分钟学 Go 语言高并发实战:高性能缓存组件开发
开发语言·后端·缓存·架构·golang