蚂蚁一面:GC垃圾回收时,内存分配和回收策略有哪些?
文章首发于公众号:腐烂的橘子
蚂蚁面试主要为电话面试,期间也会要求使用编辑器手写算法题。作为一线互联网大厂,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
- 如果为 True,检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小
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();
}
}
参考
- 深入理解 Java 虚拟机:JVM 高级特性与最佳实践. 周志明
蚂蚁一面:GC垃圾回收时,内存分配和回收策略有哪些?的更多相关文章
- JVM垃圾回收器、内存分配与回收策略
新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...
- 初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为
初步探究java中程序退出.GC垃圾回收时,socket tcp连接的行为 今天在项目开发中需要用到socket tcp连接相关(作为tcp客户端),在思考中发觉需要理清socket主动.被动关闭时发 ...
- Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法
在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...
- Java虚拟机内存分配与回收策略
内存分配与回收策略 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行, 执行的速度一般也会比较快. Full GC ...
- JVM学习十 -(复习)内存分配与回收策略
内存分配与回收策略 对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,分配规则不固定 ...
- 深入理解Java虚拟机-内存分配与回收策略
一.内存分配策略 新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Sur ...
- jvm内存分配和回收策略
在上一篇中,已经介绍了内存结构是什么样的. 这篇来介绍一下 内存是怎么分配的,和怎么回收的.(基本取自<深入理解Java虚拟机>一书) java技术体系中所提倡的自动内存管理最终可以归结为 ...
- JVM探秘:内存分配与回收策略
本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 内存分配一般关注的是对象在堆上分配的情况,对象主要分配在新生代的Eden区中,如果启用 ...
- 浅谈java内存分配和回收策略
一.导论 java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家聊聊java对象的在内存中的分配.通俗的讲,对象的内存分配 ...
- JVM——内存分配与回收策略
1.对象优先在Eden区分配 大多数情况下,对象在新生代Eden区分配.当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC. 虚拟机提供了 -XX:+PrintGCDetails这 ...
随机推荐
- 安装完exe版本jdk之后未配置java_home和path环境变量仍然可以在cmd中使用java命令原因解释
如题: 为何可以 打出Java -version的版本 ,因为jdk安装过程,拷贝了java\javac等几个命令到C:\windows\system32目录了. 如果使用javac -version ...
- Android Studio源码导入与调试
从事Android开发都需要涉及到Android源码的阅读,特别是系统应用或者Framework开发,读代码的时间远远比写代码的时间更多. 一. 生成iml与ipr 在Android Studio中导 ...
- 【Leetcode 907 907. 子数组的最小值之和】【单调栈dp】
import java.util.LinkedList; class Solution { public int sumSubarrayMins(int[] arr) { int n = arr.le ...
- [剑指 Offer II 114. 外星文字典] 拓扑排序
import java.util.*; class Solution { public static void main(String[] args) { Solution solution = ne ...
- Bootstrap前端开发框架
一 Bootstrap 简介 Bootstrap 来自 Twitter(推特),是目前最受欢迎的前端框架.Bootstrap 是基于 HTML.CSS 和 JAVASCRIPT 的,它简洁灵活,使得 ...
- 记录--vue3 + mark.js | 实现文字标注功能
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 页面效果 具体实现 新增 1.监听鼠标抬起事件,通过window.getSelection()方法获取鼠标用户选择的文本范围或光标的当前位 ...
- python批量发邮箱
1.首先登录邮箱中开启服务 2.获取到授权码后复制下来,放入如下含授权码的引号中: 1 smtp_obj.login("**********@qq.com", "授权码& ...
- 慎用django orm的update_or_create方法
公司一个线上招聘项目,后端采用Django开发,数据库使用MySQL.最近一次线上招聘会活动,因短时间大量用户涌入,被吐槽服务响应时间过长.后端和运维人员经排查,定位到MySQL数据库有死锁 根据错误 ...
- ZYNQ7000系列学习之TF卡读写(2)
ZYNQ读写实验(2) 1.实验原理 在TF卡读写实验1中,已经将每一个步骤都做完了,但是最后得到的结果是错误的.那个时候由于TF没有格式化,显示的是错误信息.在格式化后,再次实验,得到了预期的结果. ...
- ArkUI中的线程和看门狗机制
一.前言 本文主要分析ArkUI中涉及的线程和看门狗机制. 二.ArkUI中的线程 应用Ability首次创建界面的流程大致如下: 说明: • AceContainer是一个容器类,由前端.任务执行器 ...