一、概况

理解Java虚拟机垃圾回收机制的底层原理,是系统调优与线上问题排查的基础,也是一个高级Java程序员的基本功,本文就针对Java垃圾回收这一主题做一些整理与记录。Java垃圾回收器的种类繁多,它们的设计要在吞吐量(内存空间)与实时性(用户线程中断)方面进行权衡,各个垃圾回收器的适应场景也不尽相同(如:桌面应用,web应用),因此,这里我们只讨论JDK8下的默认垃圾回收器,毕竟目前JDK8版本是业界的主流(占80%),并且我们只讨论堆内存空间的垃圾回收。

JDK8下的默认垃圾回收器:UseParallelGC : Parallel (新生代)+ (老年代)堆内存回收机制

二、如何判断对象是否可回收?

首先思考一个问题,内存堆中那么多对象,回收器要回收哪些对象?怎么判断出这些要回收的对象呢?因此对于垃圾回收,判断并标识对象是否可回收是第一步。从理论层面来说,判断对象是否可回收一般两种方法。

第一种、引用计数器算法:每当对象被引用一次计数器加1,对象失去引用计数器减1,计数器为0是就可以判断对象死亡了。这种算法简单高效,但是对于循环引用或其他复杂情况,需要更多额外的开销,因此Java几乎不使用该算法。

第二种、根搜索算法-可达性分析算法:所谓可达性分析是指顺着GCRoots根一直向下搜索(用一个成语概括就是“顺藤摸瓜”),整个搜索的过程就构成了一条“引用链”,只要在引用链上的对象叫做可达,在引用链之外的(说明跟GCRoots没有任何关系)叫不可达,不可达的对象就可以判断为可回收的对象。 哪些对象可作为GCRoots对象呢? 包括如下:

  • 虚拟机栈帧上本地变量表中的引用对象(方法参数、局部变量、临时变量)
  • 方法区中的静态属性引用类型对象、常量引用对象
  • 本地方法栈中的引用对象(Native方法的引用对象)
  • Java虚拟机内部的引用对象,如异常对象、系统类加载器等
  • 所以被同步锁(synchronize)持有的对象
  • Java虚拟机内部情况的注册回调、本地缓存等

如果对虚拟机的内存布局与运行流程有所了解的话,这些作为GCRoots都很好理解,它们是程序运行时的源头,程序的正常运行必须依赖它们,而与这些源头没有任何关系的对象,即可视为可回收对象。就好比“瓜从藤上掉下来了, 那这瓜肯定也没有用了”

​GCRoots可达性分析 不可达对象

可达性分析

可达性分析从理论上很好理解,但在垃圾收集器具体运行时,要考虑的问题不知道要复杂多少倍,因为在可达性分析的同时,程序也是在并行运行着,整个内存堆的状态随着程序的运行是实时变化的,要实

现分析结果与内存状态的一致性,就必须要暂停用户线程,在一个快照去进行分析。

三、垃圾回收算法

可达性分析解决了判断对象是否可回收的问题,那么在垃圾回收时内存空间会发生哪些变化呢?这就是垃圾回收算法要讨论的问题,我们根据算法对内存采取的不同操作,可将垃圾回收算法分为3种,标记-清除算法、标记-复制算法、标记-整理算法。

3.1 标记-清除算法

根据名称就可以理解改算法分为两个阶段:首先标记出所有需要被回收的对象,然后对标记的对象进行统一清除,清空对象所占用的内存区域,下图展示了回收前与回收后内存区域的对比,红色的表示可回收对象,橙色表示不可回收对象,白色表示内存空白区域。

标记-清除算法 垃圾回收前后内存区域对比

标记-清除算法的两个缺点:

第一个:是执行效率不可控,试想一下如果堆中大部分的对象都可回收的,收集器要执行大量的标记、收集操作。

第二个:产生了许多内存碎片,通过回收后的内存状态图可以知道,被回收后的区域内存并不是连续的,当有大对象要分配而找不到满足大小的空间时,要触发下一次垃圾收集。

3.2 标记-复制算法

针对标记-清除算法执行效率与内存碎片的缺点,计算机科学家又提出了一种“半复制区域”的算法。

标记-复制算法将内存分为大小相同的两个区域,运行区域,预留区域,所有创建的新对象都分配到运行区域,当运行区域内存不够时,将运作区域中存活对象全部复制到预留区域,然后再清空整个运行区域内存,这时两块区域的角色也发生了变化,每次存活的对象就像皮球一下在运行区域与预留区域踢来踢出,而垃圾对象会随着整个区域内存的清空而释放掉,内存前后的状态参考下图:

​标记-复制算法回收前后内存对比

标记-复制算法在大量垃圾对象的情况下,只需复制少量的存活对象,并且不会产生内存碎片问题,新内存的分配只需要移动堆顶指针顺序分配即可,很好的兼顾了效率与内存碎片的问题。

标注-复制算法也存在缺点

预留一半的内存区域未免有些浪费了,并且如果内存中大量的是存活状态,只有少量的垃圾对象,收集器要执行更多次的复制操作才能释放少量的内存空间,得不偿失。

3.3 标记-整理算法

标记-复制算法要浪费一半内存空间,且在大多数状态为存活状态时使用效率会很低,针对这一情况计算机科学家又提出了一种新的算法“标记-整理算法”,标记整理算法的标记阶段与其他算法一样,但是在整理阶段,算法将存活的对象向内存空间的一端移动,然后将存活对象边界以外的空间全部清空,如下图所示:

​标记-整理算法回收前后内存对比

标记整理算法解决了内存碎片问题,也不存在空间的浪费问题,看上去挺美好的。但是,当内存中存活对象多,并且都是一些微小对象,而垃圾对象少时,要移动大量的存活对象才能换取少量的内存空间。

不同的垃圾回收算法都有各自的优缺点,适应于不同的垃圾回收场景

四、新生代、老年代堆内存结构

Java 堆内存空间新生代、老年代是如何划分的?对象创建后是如何分配到不同的区域的?结合下图可以知道,整个堆内存被分为了2个大的区域,新生代,老年代,默认情况下新生代占1/3的空间,老年代占2/3的空间,新生代又分为两个区 Eden区Survial区,Survial又分为S0、S1区 默认各占8/10与1/10,1/10的空间

​年轻代 老年代 堆空间结构

为什么要这么设计呢?为什么要分那么多不同的内存区域干嘛?这是由对象的生命周期特征、与各类垃圾回收算法的优缺点所决定的,这正式垃圾回收器设计的理论基础。经过统计分析,大多数应用程序对象生命周期符合两个特征:

垃圾回收的理论基础

  • 绝大多数的对象都是“朝生夕灭”的,既创建不久即可消亡;
  • 熬过越多此垃圾回收过程的对象就越难以消亡;

一块独立的内存区域只能使用一种回收算法,根据对象生命周期特征,将其划分到不同的区域,再对特定区域使用特定的垃圾回收算法,只有这样才能将垃圾算法的优点发挥到极致,这种组合的垃圾回收算法叫:分代垃圾算法。。比如:在新生代使用标记-复制算法,在老年代使用标记-整理算法。

五、堆内存回收过程详解

我们分析了如何判断对象是否可回收,还有3中基础的垃圾回收算法,以及年轻代、老年代的内存区域划分与原因。接下来我们就一步一步来分析堆内存的回收流程。

5.1 内存初始状态

假设在第一垃圾回收之前,内存中的状态如图所示Eden区有2个存活对象,3个垃圾对象,内存的可用区域已经所剩无几,Survivor区因为还没有进行任何MinorGC所以是空的,有1个大对象直接分配到了老年代,

垃圾回收初始状态

5.2 第1次执行MinorGC后状态

当新的对象分配到Eden区,发现内存空间不够,于是触发第一次MinorGC,垃圾回收器首先将Edne区中的两个存活对象复制到S0区,然后在清空Eden区的空间,如下图:

​第一次MinorGC内存状态

5.3 程序运行一段时间后状态

经过第1次MinorGC程序再运行一段时间后,堆内存状态如下:Eden区又产生了大量的对象,并且大部分对象都可回收状态,这也符合对象“朝生夕灭”的特征,S0区中也有1个对象可以回收,S1与老年代没有变化,在这种状态下,如果新对象分配再次触发MinorGC会发生什么呢?

​程序运行一度时间后的状态

5.4 执行第2次MinorGC后状态

新对象分配Eden区空间不足,又触发了第二次MinorGC,第二次MinorGC与第一次GC时在Eden区的操作是一样的:将Eden区存活的对象复制到S1区,然后在清空整个Eden区,同时也将S0区存活的对象复制到S1区并将对象的年龄加1,再清空S0区,GC后的状态如下图所示:

​执行第二次MinorGC后状态

5.5 第2MinorGC程序运行一段时间后状态

经过第二MinorGC后程序又运行了一段时间,Eden区中有生成了很多对象,S1区也有一个对象可回收。

第二MinorGC程序运行一段时间后状态

5.6 第15MinorGC后内存状态

在接下来的每次MinorGC时,都是第二次一样,从Eden区和survivor非空白区移动存活对象到survivor区中空白区域,并清空这两个区域内存空间,存活对象每此从survivor两个区域移动一次,对象年龄加1,下图表示经过了15次MinorGC后的堆内存状态。

​经过15次MinorGC后的内存状态

对于年轻代区域的内存收集,使用的是标记-复制算法,只是为了减少复制算法空白区域的内存浪费,并不是将内存一份为二,而是巧妙的将内存分为三个区域,预留的空白区域只占整个年轻代区域的1/10。

5.7 对象如何进入老年代

以上是年轻代的分配与回收问题,那对象如何进入老年代呢?个人认为对象进入老年代,可以分为2种类型6种情况。

​对象晋升入老年代

第一种类型--直接分配:对象创建时直接分配到老年代具体分为3种情况。

  • 超过虚拟机PretenureSizeThreshold参数设置大小的对象,该参数的默认值是0,也就是说任何大小的对象都会先分配到Eden区。
  • 超过Eden大小的对象
  • 如果新生代分配失败,一个大数组或者大字符串

第二种类型--从年轻代晋升:从年轻代空间晋升到老年代也可分为3种情况。

  • 新生代分配担保,在执行MinorGC时要将Eden区存活的对象复制到Survivor区,但是Survivor区默认空间是只有新生代的2/10,实际使用的只有1/10,当Survivor区内存不够所有存活对象分配时,就需要将Survivor无法容纳的对象分配到老年代去,这种机制就叫分配担保。
  • 对象年龄超过虚拟机MaxTenuringThreshold的设置值,最大为15,
  • Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半(TargetSurvivorRatio),年龄大于或等于该年龄的对象直接进入老年代。

内存分配担保机制

在执行MinorGC时要将Eden区存活的对象复制到Survivor区,但是Survivor区默认空间是只有新生代的2/10,实际使用的只有1/10,当Survivor区内存不够所有存活对象分配时,就需要Survivor无法容纳将对象分配到老年代空间中,这种机制就叫分配担保,但是,老年代的空间也是有限的,如果老年代中空间也不够的话,那只能乖乖的执行一次FullGC了。

5.8 老年代回收算法-FullGC

当有对象要进入老年代,而老年代空间又不足时就会触发FullGC,当然,反过来说触发FullGC的条件不仅仅只是老年代空间不足,FullGC使用的算法是上面说的标记-整理算法。

​完整堆内存回收过程

六、总结

判断对象是否可以回收是垃圾回收的基础与前提,通过可达性分析从GCRoots开始进行"顺藤摸瓜"找到不可达对象(可回收)。

对象生命周期的特征"朝生夕灭"与"越战越强"是垃圾回收算法的理论基础。

基础的垃圾回收算法有3种分别是 标记-清除算法、标记-负责算法、标记整理算法,都有各自的适应场合与优缺点。

分代垃圾算法根据对象生命周期的特征,将其划分到不同的区域,从而使用最适合的垃圾算法来进行优化。

在JDK8默认的配置下使用 新生代,老年代的垃圾回收策略,新生代区域使用标记-复制算法,老年代区域使用标记-整理算法。

作者:攀岩飞鱼

原文链接:https://xie.infoq.cn/article/9d4830f6c0c1e2df0753f9858

内存分配算法java_被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解...相关推荐

  1. 6种java垃圾回收算法_被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解...

    一.概况 理解Java虚拟机垃圾回收机制的底层原理,是系统调优与线上问题排查的基础,也是一个高级Java程序员的基本功,本文就针对Java垃圾回收这一主题做一些整理与记录.Java垃圾回收器的种类繁多 ...

  2. 被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

    一.概况 理解Java虚拟机垃圾回收机制的底层原理,是系统调优与线上问题排查的基础,也是一个高级Java程序员的基本功,本文就针对Java垃圾回收这一主题做一些整理与记录.Java垃圾回收器的种类繁多 ...

  3. go java 垃圾回收_Go/Java垃圾回收算法对比解析

    原标题:Go/Java垃圾回收算法对比解析 导读:GC 是大部分现代语言内置的特性,本文作者针对 Go 语言声称的 10ms 以下的 GC 停顿进行了深入分析,还同 Java 的垃圾收集器做了对比.G ...

  4. 6种java垃圾回收算法_学习java垃圾回收

    垃圾回收(GC)一直是Java受欢迎背后的重要特性之一.垃圾回收是Java中用于释放未使用的内存的机制.本质上,它追踪所有仍在使用的对象,并将剩下的标记为垃圾.Java的垃圾回收被认为是一种自动内存管 ...

  5. 6种java垃圾回收算法_Java垃圾回收机制

    Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给 ...

  6. Java垃圾回收算法详解

    1 概述 在前一篇文章中讲到了Java虚拟机的基础知识和运行时数据区的划分,在运行时数据区的划分中,可分为线程共享区域和线程私有区域,而Java的垃圾回收就发生在线程共享区域中,更直观的说法就是Jav ...

  7. 6种java垃圾回收算法_Java垃圾回收算法

    主要根据以下3篇博客做的整理 http://blog.csdn.net/zsuguangh/article/details/6429592 http://www.cnblogs.com/ywl925/ ...

  8. Java垃圾回收算法以及垃圾回收器

    文章目录 前言 JVM 内存区域 如何识别垃圾 引用计数法 可达性算法 虚拟机栈(栈帧中的本地变量表)中引用的对象 方法区中类静态属性引用的对象 方法区中常量引用的对象 本地方法栈中 JNI 引用的对 ...

  9. 7张图讲透Java垃圾回收算法!学妹直呼666!!!

    JVM 在垃圾回收的时候: ① 到底使用了哪些垃圾回收算法? ② 分别在什么场景下使用? ③ 各自的优缺点? 下面就来正式的介绍下垃圾回收算法 标记-清除 标记清除是最简单和干脆的一种垃圾回收算法,他 ...

最新文章

  1. 关于序列化的 10 几个问题,你顶得住不?
  2. 如何制作U盘启动菜单
  3. bat批处理for /f命令
  4. Python 柱状图 横坐标 名字_Python气象绘图教程(四)
  5. 广州市岑村教练场考科目二,惊险通过,经验总结
  6. 【maven】idea左侧External Libraries里,没有Maven的依赖包 代码飘红
  7. string 与BigDecimal互转
  8. 小米MIX4最新渲染图曝光;苹果首款OLED iPad将于2023年问世;格力将引进鸿蒙操作系统|极客头条...
  9. Java:Linux上java -jar xxx.jar命令执行jar包时出现Error: Invalid or corrupt jarfile xxx.jar解决方案
  10. 蓝桥杯2016年第七届C/C++省赛B组第五题-抽签
  11. 从B树、B+树、B*树谈到R树
  12. php 文件目录操作相关函数file_exists mkdir chmod touch
  13. 手机html禁止复制,网页禁止复制文字?学会这六招,全网内容任你免费复制!...
  14. AutoRunner不能录制脚本的解决办法
  15. CSS选择器(id选择器,包含选择器,标签名选择器,类选择器,属性选择器,通配符选择器,伪类选择器,相邻选择器,选择器的优先级,子选择器)
  16. 第一周 周报
  17. python3or5的值_详解python中and和or的返回值
  18. el-checkbox多选框点击第一次不能勾选,第二次才会勾选
  19. 信息安全学习4. 重放攻击的概念与防范
  20. AMD与Intel现在的情况20120906

热门文章

  1. 基于 VLANIF 的公司网络搭建
  2. 埃博拉与现代微生物战争
  3. Vue+Element UI 下,如何给一个表单设置自定义规则或多个验证规则(:rules)
  4. 烧屏问题难以解决,或成为LG推广OLED的重要障碍
  5. 我要自学网小程序开发
  6. 安装 timescaledb 使用navcat连接 创建 hypertable
  7. 浏览器打开控制台触发事件
  8. Luogu CF786B Legacy
  9. vue拖拽功能,提高你的开发效率
  10. Java发送Http请求之——发送请求参数在Body中Get请求