蚂蚁一面:GC垃圾回收时,内存分配和回收策略有哪些?

news/发布时间2024/5/18 14:16:09

文章首发于公众号:腐烂的橘子

蚂蚁面试主要为电话面试,期间也会要求使用编辑器手写算法题。作为一线互联网大厂,Java 基础知识是必备的,其中垃圾回收也是面试过程中的重中之重。

Java 内存的自动管理,关键要解决内存的自动分配和自动回收。本文基于周志明的经典著作《深入理解 JAVA 虚拟机》介绍了内存分配会回收的一些基本策略。我们一方面要理解这些基本策略,另一方面要会通过代码验证、测试这些回收策略,且掌握这些分析方法比策略本身更有效。

1. 新对象优先分配在 Eden 区

当 Eden 区没有足够的空间存放对象时,将触发一次 Minor GC。

public class Allocation {private static final int _1MB = 1024 * 1024;/*** -Xms 初始堆大小* -Xmx 最大堆大小* -Xmn 新生代大小* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8*/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];}public static void main(String[] args) {testAllocation();}
}

2. 大对象直接分配在老年代

避免创建“朝生夕灭”的“短命大对象”。创建大对象会导致明明还有很多内存却提前触发了垃圾回收,以获取足够的连续空间才能安置好它们,而复制对象时,意味着高额的复制开销。在 HotSpot 中可以使用 -XX:PretenureSizeThreshold 指定大于该值的对象直接在老年代分配,避免在 Eden 区和 两个 Survivor 区之间来回复制,产生大量的内存复制操作。

public class Allocation2 {private static final int _1MB = 1024 * 1024;/*** VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 * -XX:PretenureSizeThreshold=3145728*/public static void testPretenureSizeThreshold() { byte[] allocation;allocation = new byte[4 * _1MB]; //直接分配在老年代中}public static void main(String[] args) {testPretenureSizeThreshold();}
}

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

如果对象在 Survivor 区中每熬过一次 Minor GC,年龄就会增加一次。年龄是虚拟机为每个对象定义的 Age 计数器,保存在对象头中。

在 HotSpot 虚拟机中,对象在堆内存中存储布局分别为对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。其中对象头除了保存 GC 分代年龄外,还保存了哈希码(HashCode)、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,这些被称为 Mark Word。

当年龄到达 15 岁时,就会晋升到老年代。15 是默认值,可以通过 -XX: MaxTenuringThreshold 设置其他阈值。

public class Allocation3 {private static final int _1MB = 1024 * 1024;/*** VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:Survivor-Ratio=8 -XX:MaxTenuringThreshold=1 * -XX:+PrintTenuringDistribution*/@SuppressWarnings("unused")public static void testTenuringThreshold() {byte[] allocation1, allocation2, allocation3;// 什么时候进入老年代决定于XX:MaxTenuringThreshold 设置allocation1 = new byte[_1MB / 4];allocation2 = new byte[4 * _1MB];allocation3 = new byte[4 * _1MB];allocation3 = null;allocation3 = new byte[4 * _1MB];}public static void main(String[] args) {testTenuringThreshold();}
}

4. 动态对象年龄判定

HotSpot 虚拟机并不要求年龄必须达到 15 才进入老年代,还会根据一个动态机制来判断,即:如果在 Survivor 空间中相同年龄的所有对象总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到 -XX:MaxTenuringThreshold=1 中配置的年龄。

public class Allocation4 {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 testTenuringThreshold() {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];}public static void main(String[] args) {testTenuringThreshold();}
}

5. 空间分配担保

这里的规则不难,但是需要理解。在每次 Minor GC 之前,虚拟机都会检查老年代最大可用连续空间是否大于新生代的所有对象总空间,如果成立,我们认为这次 Minor GC 是安全的。因为 Minor GC 后的垃圾会进入老年代,老年代的空间够用,所以是安全的。

如果老年的空间不够呢?虚拟机会进行如下步骤:

  • 检查 -XX:HandlePromotionFailur 的值
    • 如果为 True,检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小
      • 是:进行 Minor GC,因为是根据“经验”判断的,所以此次 GC 可能有风险
      • 否:进行 Full GC,不去“冒险”
    • 如果为 False,进行 Full GC

JDK6 update24 之后的规则变为:只要老年代剩余连续空间大于新生代对象总大小,就会进行 Minor GC,否则进行 Full GC。

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

参考

  1. 深入理解 Java 虚拟机:JVM 高级特性与最佳实践. 周志明

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ulsteruni.cn/article/33086054.html

如若内容造成侵权/违法违规/事实不符,请联系编程大学网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

OOP第一次博客作业

一. 前言 在过去三周里每周都有一次PTA大作业,每次的内容都是在前一次的内容上更加复杂,也更完善。这几次作业考察的不只是基础的Java语法,还有正则表达式,动态列表等,更注重语句之间的逻辑性。从前几次简单的程序要求一下跳到这么困难的题,让人有点难以适应。特别是不定…

OOP第一阶段题集总结

一.前言知识点:数组,字符串的使用,链表,hashmap,泛型的使用,正则表达式的使用,类的设计,类与类之间的关系,单一职责。 题量:题目数量为5+4+3,数量适中,其中都是前几题较简单,最后一题较为复杂,且每一次都是在前一次的基础上进行迭代。 难度:前几题基础简单,最后…

科研软件

【专业软件】科研软件看这里,方便你我他 封面目录页 1. 科研专业软件1.1. 数值模拟1.1.1. 有限元 1.1.2. 离散元1.2. 顶级图像分析软件2. 实用小工具2.1. GIF制作2.1.1. ScreenToGif2.2. 文字识别2.2.1. 公式识别2.3. 网络限制版资源获取2.3.1. yotub…

23201228-第一次Blog

一、前言: 从大一下学期开始学习java到现在,已经完成了三次PTA用java实现的大作业,三次PTA作业的难度在逐渐增大,每次最后一题都是从第一次PTA大作业里迭代而来,难度很大且每次提升,涉及的内容有很多,比如类,方法,Arraylist等,但最主要的还是类的设计,通过这三次作业…

关于题目集1~3的总结

前言 前三次pta作业最后一题都是答题判题程序,题目难度逐级提升但写完后收获也不小。首先一点是需求分析,不应上来就写代码而是从业务需求整体分析,在确定好程序的层次结构再开始实现相应的功能。 在这三次作业中,将所学的编程知识很好地运用,其次,三次作业也同样考验我们…

blog1 1--3周PTA训练总结

一.前言: 在学习过C语言之后,面向对象的程序设计在本学期如期开启。该课程的编程语言是java,java与所学过的C语言有诸多相似之处,与C语言课程所不同的是,这门课程注重的是面向对象,如果说C语言是语法的学习,那么java就是其实战应用的学习,这门课的学习更让我深刻的感受…