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

蚂蚁面试主要为电话面试,期间也会要求使用编辑器手写算法题。作为一线互联网大厂,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 高级特性与最佳实践. 周志明

蚂蚁一面:GC垃圾回收时,内存分配和回收策略有哪些?的更多相关文章

  1. JVM垃圾回收器、内存分配与回收策略

    新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...

  2. 初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为

    初步探究java中程序退出.GC垃圾回收时,socket tcp连接的行为 今天在项目开发中需要用到socket tcp连接相关(作为tcp客户端),在思考中发觉需要理清socket主动.被动关闭时发 ...

  3. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

  4. Java虚拟机内存分配与回收策略

    内存分配与回收策略 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行, 执行的速度一般也会比较快. Full GC ...

  5. JVM学习十 -(复习)内存分配与回收策略

    内存分配与回收策略 对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,分配规则不固定 ...

  6. 深入理解Java虚拟机-内存分配与回收策略

    一.内存分配策略 新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Sur ...

  7. jvm内存分配和回收策略

    在上一篇中,已经介绍了内存结构是什么样的. 这篇来介绍一下 内存是怎么分配的,和怎么回收的.(基本取自<深入理解Java虚拟机>一书) java技术体系中所提倡的自动内存管理最终可以归结为 ...

  8. JVM探秘:内存分配与回收策略

    本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 内存分配一般关注的是对象在堆上分配的情况,对象主要分配在新生代的Eden区中,如果启用 ...

  9. 浅谈java内存分配和回收策略

    一.导论 java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家聊聊java对象的在内存中的分配.通俗的讲,对象的内存分配 ...

  10. JVM——内存分配与回收策略

    1.对象优先在Eden区分配 大多数情况下,对象在新生代Eden区分配.当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC. 虚拟机提供了 -XX:+PrintGCDetails这 ...

随机推荐

  1. SAE自动驾驶分级介绍

    SAE International 国际自动机工程师学会(原译:美国汽车工程师学会)英文全程为:Society of Automotive Engineers International,是一个全球性 ...

  2. day16--Java常用类04

    Java常用类 4.字符串相关类练习 4.1StringBuilder练习 package li.normalclass.stringbuilder; public class TestBuffer ...

  3. 关于初始化page入参的设计思路

    最近在重构老的代码,在写的过程中发现之前的逻辑如果遇到没有入参pageNo会Npe,于是乎我想找找公司项目有啥方式处理page入参的有两种如下 使用三元表达式直接判断是否null,然后赋值 使用map ...

  4. windows下删除文件时提示“操作无法完成 因为文件已在。。。”解决方案

    解决方案:利用"资源监视器",如上图: 具体步骤: win+r,输入perfmon打开资源监视器 或者 右键--Windows 7任务栏--启动任务管理器--性能--资源监视器-- ...

  5. Windows下写脚本无法运行在linux上?怎麽办?

    Windows下写脚本无法运行在linux上?怎麽办? $'\r': command not found的解决方法 在Linux系统中,运行Shell脚本,出现了如下错误: one-more.sh: ...

  6. Python简单程序设计(Unicode编码与字符间转换篇)

    如题: (误以为本题存在两者之间互相转换,后附上逆过程代码) 解题方式如下: 逆过程:

  7. OpenLayers 点击显示经纬度Demo

    这里给大家分享我在OpenLayers 地图开发工作中总结出的一下代码和注意点,希望对大家有所帮助 效果如下: 核心代码展示:附带讲解注释 var map = new ol.Map({ // 初始化地 ...

  8. 攻防世界 gametime 使用IDA pro+OD动调

    自学犟种琢磨动调的一个记录,算是第一次动调的新手向,大佬请飘过 题目 准备工作--IDA pro(32X) 下载得到一个exe文件,首先丢到PE里面--无壳,32bit 丢到IDA pro(x32)里 ...

  9. 用于vivado的TCL脚本创建工程

    用于实现在脚本所在的目录创建工程并初始化工程 点击查看代码 #获取脚本所在的文件 variable My_File [file normalize [info script]] #prj_path_f ...

  10. KingbaseES V8R6备份恢复案例之---备份crond计划任务的调整

    案例说明: 在KingbaseES V8R6数据库通常备份建立的crond任务,默认由root用户建立,写入到/etc/cron.d/KINGBASECRON文件中,在有的生产环境限制了普通用户使用r ...