一张图看懂JVM之垃圾回收算法详解

简介:

42c80f9d08dc4d239811299f56e974fb0c258676

导读

在之前的内容中,我们通过一张图的方式(图),从总体上对JVM的结构特别是内存结构有了比较清晰的认识,虽然在JDK1.8+的版本中,JVM内存管理结构有了一定的优化调整。主要是方法区(持久代)取消变成了直接使用元数据区(直接内存)的方式,但是整体上JVM的结构并没有大改,特别是我们最为关心的堆内存管理方式并没有在JDK1.8+的版本中有什么变化,所以图中的结构整体上是没有什么不准确的,之所以将方法区以及持久代标注出来,主要还是为了起到对比认识的作用,大家知道就可以了。

关于持久代元数据区的使用问题,目前可以理解就是使用的物理内存,理论上是不受JVM自动内存回收机制管理的,如果不设置参数大小默认最大使用限制就是操作系统可用物理内存的大小,设置了-XX:MetaspaceSize参数的话,JVM就会在使用物理内存空间时自己进行限制。

至于直接内存与物理内存到底是不是一回事,我认为对于我们理解上没有区别,只是概念的区别,另外就是对这块内存使用细节上的区别,如果不受JVM的自动回收管理,那么怎么管理呢?说到底还是JVM本身在直接使用物理内存或者说是直接内存(用时直接“malloc”物理内存区域,而不再是JVM进程启动时初始化的内存区域),还有一种概念叫native memory,说实话我暂时还不理解他们到底有啥区别,如果大家对这些概念有更好的认识,也可以给我留言哦!之所以对这几个问题做一些笔墨的说明,主要是在之前的文章中大家对此提出了疑问,所以正好在这节的内容中进行下阐述。

回到今天的主题,我们知道JAVA最大的优点就是可以实现自动内存管理,这极大的便利了JAVA程序员,降低了使用成本。但这也使得平时我们在使用JAVA编程时不太关注JVM到底是怎样进行内存回收的,只有在需要实际对JVM进行系统性能调优,这里的场景可能是在系统面临极致性能优化要求时,我们才发现需要对JAVA的整体内存结构以及内存回收机制要有一定的认识和了解才行。

在的图中,我们也大致对整个垃圾回收系统进行了标注,这里主要涉及回收策略回收算法、垃圾回收器这几个部分。形象一点表述,就是JVM需要知道那些内存可以被回收,要有一套识别机制,在知道那些内存可以回收以后具体采用什么样的回收方式,这就需要设计一些回收算法,而具体的垃圾回收器就是根据不同内存区域的使用特点,采用相应地回收策略和算法的具体实现了。

在图中,我们也标注了不同垃圾回收器所适用的特定内存区域,对于JVM垃圾回收这块的优化,就是我们需要在了解这些垃圾回收算法、垃圾回收器特点后能够根据自己应用的场景选择合适的垃圾收集器,以及各区域垃圾收集器的搭配关系。下面我们就从这几个方面给大家介绍,JVM的垃圾回收相关的知识点。

回收策略

我们知道,JVM进行内存回收的主要目的是为了回收不再使用的内存,因为在进行JAVA程序编写时,我们只有new的操作,而不需要收工释放不再使用的空间,如果这些空闲内存不能及时被回收,很快我们的JVM内存空间就会泄露(新申请内存空间的操作失败,导致程序报错),所以回收不再使用的内存的目的则是为了及时释放空间,腾笼换鸟,以防止内存泄漏

那么问题来了,JAVA程序申请了那么多的内存空间,那些内存才能被认定是不再使用的内存呢?搞错了,如果把正在被程序使用的内存给释放了,程序逻辑就空指针异常了!

我们知道在JVM中内存分配的基本粒度主要是对象、基本类型。而基本类型的使用主要是包括在对象中的局部变量,所以回收对象所占用的内存是JAVA垃圾回收的主要目标。

那么如何判断对象是处于可回收状态的呢?在主流的JVM中是采用“可达性分析算法”来进行判断的。

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,并从这些节点开始往下进行搜索,搜索走过的路径我们称之为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,我们就称之为对象引用不可达,则证明这个对象是不可用的,就可以暂时判定这个对象为可回收对象。示意图如下:

1bf7871c1aeaef8b6d0cf93fef7960aadcae5208

在图中虽然Obj F与Obj J之间互相有关联但是它们到GC Roots是不可达的,所以将会被判定为可回收对象。既然如此,什么样的对象可以作为GC Roots对象呢?

在JAVA中可以被作为GC Roots的对象主要是:虚拟机栈-栈帧中的本地变量表所引用的对象、方法区(<JDK1.8)中类静态属性所引用的对象/常量属性所引用的对象、本地方法栈中引用的对象。

这里还需要注意一个小的细节,就是被判定为对象不可达的对象也并非会被立刻回收,在学习JAVA语法是我们应该学习过finalize()方法,如果对象重写了finalize方法,并重新把this关键字赋值给了某个类变量或对象的成员变量的话,该对象就会被"救活",具体过程可参考上图所示,只是这种方式并不鼓励大家使用,了解下就行。

在关于如何判定对象是否属于不再使用的内存时,还有个通常会被大家错误认为是JVM使用的方式-“引用计数法”,事实上引用计数法的实现比较简单,判定效率也比较高,在Python语言中就使用了这种算法进行内存管理,但是它有一个比较难解决的对象之间循环引用的问题,所以在JAVA虚拟机里并没有选用“引用计数法”来管理内存。这个问题很多人都会搞错,包括有很多年开发经验的程序员,需要大家注意下!

回收算法

在JVM中主要的垃圾收集算法有:标记-清除、标记-清除-压缩(简称“标记-整理”)、标记-复制-清除(简称“复制”、分代收集算法。这几种收集算法互相配合,针对不同的内存区域采取对应的收集算法实现(这里具体是由相应的垃圾收集器实现)

下面我们就分别来看下这几种收集算法的特点:

1)、标记-清除

标记-清除算法是最为基础的一种收集算法,算法分为:“标记”和“清除”两个阶段。首先标记出所有需要回收的对象(标记的过程就是上面介绍过的根节点可达算法),在标记完后统一回收所有被标记对象占用的内存空间。

示意图如下:

ef4e1a99c0a3adfae877603adfe3d498db94c2d7

这种收集算法的优点是简单直接,不会影响JVM进程的正常运行。而其缺点也是非常明显,首先,这样的回收方式会产生大量不连续的内存碎片,不利于后续连续内存的分配;其次,这种方式的效率也不高。

2)、标记-复制-清除

这种算法的思路是将可用的内存空间按容量划分为大小相等的两块,每次只使用其中一块。当这一块使用完了,就将还存活着的对象复制到另外一块上面(移动堆顶指针,按顺序分配内存),然后再把已使用过的内存空间一次清理掉。

示意图如下:

9950839ba9fed4786a3192152c7e1f5e2dea4365

这种收集方式比较好的解决了效率和内存碎片的问题,但是会浪费掉一般的内存空间。目前此种算法主要用于新生代回收(文顶的图中有标注)。

因为新生代的中98%的对象都是很快就需要被回收的对象,这一点大家在编程时可以体会到,所以并不需要1:1的比例来划分内存空间,在新生代中JVM是按照“8:1:1”的比例(文顶图中有标注)来将整个新生代内存划分为一块较大的Eden区和两块较小的Survivor区(S0、S1)。

每次使用Eden区和其中一个Survivor区,当发生回收时将Eden区和Survivor区中还存活的对象一次性复制到另一块Survivor区上,最后清理掉Eden区和刚才使用过的Survivor区。理想情况下,每次新生代中的可用空间是整个新生代容量的90%(80%+10%),只会有10%的内存会被浪费。实际情况中,如果另外一个10%的Survivor区无法装下所有还存活的对象时,就会将这些对象直接放入老年代空间中(这块在后面的分代回收算法会说到,这里先了解下)。

3)、标记-清除-压缩

如果在对象存活率较高的情况下,仍然采用复制算法的话,因为要进行较多的复制操作,效率就会变得很低,而且如果不想浪费50%的内存空间的话,就还需要额外的空间进行分配担保以应对存活对象超额的情况显然老年代不能采用2)中的复制算法。

根据老年代的特点,标记-清除-压缩(简称标记-整理)算法应运而生,这种算法的标记过程仍然与“标记-清除”算法一样,只是后续的步骤不再是直接清除可以回收的对象,而是将所有存活的对象都向一端移动后,再直接清理掉端边界以外的内存。

示意图如下:

d0d93fc6eefe1561a725e197dd2768595a7401ff

4)、分代回收算法

实际上在讲解复制算法时已经涉及到了分代回收的内容,这种算法根据对象存活周期的不同将内存划分为几块,Java中主要是新生代、年老代这样就可以根据各个年代的特点,采用合适的收集算法了在文顶的图中已经标示,新生代采用了复制算法,而老年代采用了整理算法这里就不再赘述


原文发布时间为:2018-11-7

本文作者:无敌码农

本文来自云栖社区合作伙伴“程序员小灰”,了解相关信息可以关注“程序员小灰”。

相关文章
|
2天前
|
缓存 算法 Java
JVM实战—4.JVM垃圾回收器的原理和调优
本文详细探讨了JVM垃圾回收机制,包括新生代ParNew和老年代CMS垃圾回收器的工作原理与优化方法。内容涵盖ParNew的多线程特性、默认线程数设置及适用场景,CMS的四个阶段(初始标记、并发标记、重新标记、并发清理)及其性能分析,以及如何通过合理分配内存区域、调整参数(如-XX:SurvivorRatio、-XX:MaxTenuringThreshold等)来优化垃圾回收。此外,还结合电商大促案例,分析了系统高峰期的内存使用模型,并总结了YGC和FGC的触发条件与优化策略。最后,针对常见问题进行了汇总解答,强调了基于系统运行模型进行JVM参数调优的重要性。
JVM实战—4.JVM垃圾回收器的原理和调优
|
6天前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
3天前
|
消息中间件 存储 算法
JVM实战—3.JVM垃圾回收的算法和全流程
本文详细介绍了JVM内存管理与垃圾回收机制,涵盖以下内容:对象何时被垃圾回收、垃圾回收算法及其优劣、新生代和老年代的垃圾回收算法、Stop the World问题分析、核心流程梳理。
JVM实战—3.JVM垃圾回收的算法和全流程
|
4月前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
9天前
|
监控 算法 Java
JVM—垃圾收集算法和HotSpot算法实现细节
JVM的垃圾收集算法和HotSpot的实现细节复杂但至关重要,通过理解和掌握这些算法,可以为Java应用程序选择合适的垃圾收集器,并进行有效的性能调优。选择适当的垃圾收集策略,结合合理的内存配置和日志分析,能够显著提升应用的运行效率和稳定性。
40 15
|
3月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
91 0
|
2天前
|
消息中间件 算法 Java
JVM实战—5.G1垃圾回收器的原理和调优
本文详细解析了G1垃圾回收器的工作原理及其优化方法。首先介绍了G1通过将堆内存划分为多个Region实现分代回收,有效减少停顿时间,并可通过参数设置控制GC停顿时长。接着分析了G1相较于传统GC的优势,如停顿时间可控、大对象不进入老年代等。还探讨了如何合理设置G1参数以优化性能,包括调整新生代与老年代比例、控制GC频率及避免Full GC。最后结合实际案例说明了G1在大内存场景和对延迟敏感业务中的应用价值,同时解答了关于内存碎片、Region划分对性能影响等问题。
|
3月前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
3月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
4月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
86 1

热门文章

最新文章