蚂蚁一面: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这 ...
随机推荐
- etcd每个节点都存储了完整的键值对数据集,为什么扩容etcd集群仍可分散存储压力?
etcd每个节点都存储了完整的键值对数据集,这主要是为了确保数据的一致性和高可用性.在这种设计下,任何一个节点都可以处理读取请求,并在本地提供数据,从而无需跨节点通信.这种冗余的数据存储方式也增加了系 ...
- 基于ESP8266的JSON解析实例分析
什么是JSON? JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.采用完全独立于编程语言的文本格式来存储和表示数据.其简洁和层次结构清晰的特点使得 J ...
- docker如何以root身份登录
有时候我们需要进入docker容器时以root身份进入,这边汇总了两种方式如下 第一种 docker exec -it --user=root container_id /bin/bash 第二种 d ...
- 一个简单的RTMP服务器实现 --- RTMP复杂握手(Complex Handshake)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- TP6框架--EasyAdmin学习笔记:Excel表单导入数据库
这是我写的学习EasyAdmin的第四章,这一章我给大家分享下Excel表单导入数据库的全流程需要怎么处理并提供案例 首先给大家看下这个功能的原理,下面是PHP连接打印机的代码 public func ...
- 记录--WebSocket 原理
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.什么是WebSocket WebSocket 是一种在单个TCP连接上进行全双工通信的协议.WebSocket 使得客户端和服务器之间 ...
- 记录--uniapp中生成二维码并展示
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 uniapp生成二维码并展示 1.下载weapp-qrcode.js文件并放在utils文件中链接: https://pan.baidu. ...
- 性能测试系列:Oracle数据库awr报告使用与分析
一 AWR报告生成 1.生成AWR(Automatic Workload Repository)报告:sqlplus / as sysdbaSQL>@?/rdbms/admin/awrrpt.s ...
- Jvm之用C#解析class文件
项目地址 GitHub - lxw112190/JavaClassReader: C# JavaClassReader 项目结构 一个简单的测试类 public class Test { Intege ...
- KingbaseES V8R6 中syssql_tmp目录说明
前言 不久前有前端人员咨询过一个问题,为什么syssql_tmp目录下会产生如此多的大文件. 针对这个目录的解释是:临时文件(用于排序超出内存容量的数据等操作)是在$KINGBASE_DATA/bas ...