JVM基础(5)——JVM垃圾回收算法

这篇具有很好参考价值的文章主要介绍了JVM基础(5)——JVM垃圾回收算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

一、简介

我们在前两章中,已经讲解了JVM垃圾回收的基本流程和对象存活判定的算法,但是,并没有深入垃圾回收内部的细节。本章,我们就深入垃圾回收的内部,看看JVM到底是如何进行对象内存的回收的。

二、复制算法

复制算法,主要用于 新生代 中对象的回收。其基本思路就是:将新生代内存按划分为大小相等的两块,每次只使用其中的一块,当一块内存用完了, 将存活的对象移动到另外一块上面 ,然后在把已使用过的内存空间一次清理掉。

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

2.1 算法流程

我们以示例代码来看复制算法的执行流程:

    public class Kafka {
    
        public static void main(String[] args) throws InterruptedException {
            loadReplicaFromDisk();
        }
    
        private static void loadReplicaFromDisk() {
            ReplicaManager replicaManager = new ReplicaManager();
            replicaManager.load();
        }
    }

假设程序执行到replicaManager.load(),JVM的内存数据结构如下,其中“大量垃圾对象无人引用”表示其它程序产生的垃圾对象,此时新生代中用于分配对象内存的区域也快满了,再次为对象分配内存时就会触发“Minor GC”:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

此时,一种最基本的思路就是,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。但是这种方法问题也很明显: 产生大量内存碎片,导致后续需要为大对象分配空间时内不足 。所以,JVM很少用这种方法,而是先将存活对象转移到另一块区域:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

然后一次性把原来那块内存清空:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

上述整个流程,就是所谓的复制算法: 把新生代划分为两块内存区域,只使用其中一块,当这块快满的时候,把存活对象一次性转移到另一块,保证没有内存碎片,然后清空原来那块,依次循环往复 。

2.2 算法优化

上述复制算法的缺点很明显:即 对内存的使用效率太低 。比如我们给新生代分配了1G内存,那其实只有512MB是实际使用的,很浪费内存空间。那么如何来优化呢?

我们回顾下 新生代中对象的特点:朝生暮死,也就是说新生代的绝大多数对象在经历1次GC后就会被回收掉,存活率非常低。

根据这个特点, HotSpot VM 采用了一种做法,把新生代区域划分成了三块: 1个Eden区(80%),2个Survivor区(各占10%) ,最开始,对象只在Eden进行分配:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

如果Eden区快满了,此时触发GC会将Eden区中的存活对象转移到其中一块Survivor中,同时清空Eden:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

下一次再分配空间时,依然在Eden区分配,然后触发GC,将Eden的存活对象和上一次使用的Survivor中的存活对象转移到另一块空白Survivor中,然后清空Eden和使用过的Survivor,循环往复。

这种内存划分方式的最大好处就是只有10%的空间是闲置的,无论是垃圾回收的性能、内存碎片的控制、内存使用率,都非常好。

三、标记整理算法

通过前一节,大家应该已经了解了新生代的垃圾回收算法,本节我们就来看下老年代的垃圾回收算法——标记整理算法。

3.1 何时进入老年代

这里有一个问题,新生代的对象什么时候会进入老年代?先给出一个结论,一共有五种情况:

  • 新生代对象的年龄超过一定阈值(默认15);
  • 动态年龄判断
  • 大对象直接分配
  • Survivor区空间不足
年龄阈值

之前我们提到过,新生代中的对象每逃过一轮GC,年龄都会加1,到年龄达到15时(也可以通过JVM参数-XX:MaxTenuring Threshold设置),就会被转移到老年代:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

动态年龄判断

动态对象年龄判断的规则是: Survivor区的存活对象年龄从小到大进行累加,当累加到 X 年龄时的总和大于 Survivor区空间的50%时,那么比X大的对象都会晋升到老年代。

举个例子,比如当前Survivor区的分布如下,累加结果45%小于50%:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

此时新生代GC后,有6%的对象进入Survivor区,则Survivor区分布如下图:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

这时从1岁加到4岁的对象总和51% 大于50%,但此时没有大于四岁的对象,即没有对象晋升 。此时再经过一次新生代GC后,又有40%的对象进入Survivor区,Survivor区分布如下图:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm


Survivor区的对象年龄从小到大进行累加,当累加到 3 年龄时的总和大于50%,那么比3大的都会晋升到老年代,即4岁的20%、5岁的20%晋升到老年代。

可以使用-XX:TargetSurvivorRatio来设置Survivor区空间的百分比,默认值是50

大对象

对于一些大对象,JVM会直接将其分配到老年代。通过参数-XX:PretenureSizeThrehold,可以设置阈值,单位为字节。

JVM之所以要这么做,是为了避免新生代中出现屡次逃过GC的大对象,大对象在新生代的Eden和Survivor区的来回复制开销比较大。

Survivor区空间不足

最后一种情况就是,Minor GC之后发现存活对象太多,没法放入另一块Survivor区域中,比如下面这种情况:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

这时候就必须把这些对象全部迁移到老年代去:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

3.2 空间分配担保

前面讨论了新生代的存活对象何时会转移到老年代,那么问题又来了,如果老年代区域的内存空间不足了怎么办?这里就涉及了 空间分配担保机制 。

所谓空间分配担保,指在执行任何一次Minor GC之前,JVM会检查 老年代的最大连续可用空间 是否大于 新生代所有对象的总大小 :

如果大于,说明这次Minor GC肯定是安全的,因为老年代可以容纳新生代中的所有对象;

如果小于,则 JVM 会查看-XX:HandlePromotionFailure参数值,这个参数值表示是否允许担保失败:

  • 如果允许(HandlePromotionFailure==true),则看下 老年代的最大连续可用空间 是否大于 历次Minor GC后进入老年代的对象平均大小 。如果大于,就进行minior GC,如果这次Minior GC失败了,就会进行FULL GC(所谓FULL GC,就是既对老年代进行垃圾回收,也对新生代进行垃圾回收);如果小于,先进行FULL GC,再Minor GC。
  • 如果不允许(HandlePromotionFailure==false),则直接触发FULL GC,然后再进行一次Minor GC。

如果经过上面的操作,老年代可用空间最后发现还是不够,就会导致所谓的OOM内存溢出了。

总之,空间分配担保机制的核心目的就是 避免频繁FULL GC,能先预判就先预判 ,实在不行才FULL GC,因为FULL GC的开销非常大,既要对老年代进行回收,也要对新生代进行回收。

3.3 算法流程

了解了新生代对象何时进入老年代,以及FULL GC的触发时机,我们就可以来看下老年代的 标记整理算法 的流程了。标记整理算法,其实就是先标记存活对象,然后将存活对象都向内存端边界移动,然后清理掉端边界以外的内存,这样就可以避免出现大量内存碎片。

我们通过示例来看下,假设JVM当前的内存状态如下,老年代中散落着各种存活对象:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

接着,会将存活对象都往内存的一边移动,让它们尽量紧凑,然后一次性把垃圾对象清理掉:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

四、线上示例

通过前两节的讲解,相信读者已经对新生代的复制算法、老年代的标记整理算法有所了解,本节我们将通过一个生产系统的GC案例,让大家更加透彻的理解JVM中如果进行对象分配和老年代转移,以及Minor GC和Full GC的全过程。

4.1 背景

假设现在生产环境有一套“数据计算系统”,不停地从MySQL等各类数据源提取数据到内存中进行计算,系统是分布式的。每个节点(机器)每分钟执行100次操作(提取数据并计算,每次操作耗时10s),每次操作1万条数据,每条数据大小为1KB左右,那么每次数据的总大小就是10MB:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

每台机器的配置是4核8G,JVM分配4G内存,其中新生代1.5G,老年代1.5G。

整个系统的初始背景大致就是上面这样,下面来分析可能存在的各种问题。

4.2 频繁Full GC

我们先来看下新生代的空间什么时候会被占满,按照8:1:1来分配Eden和Survivor区,如下图:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

每执行一次操作,Eden区就会填充10MB数据,一分钟执行100次操作就是1000MB,所以 Eden区基本上1分钟左右就会被占满 。再执行操作时,就会进行Minor以回收一部分的垃圾对象:

首先,检查老年代的连续可用内存空间是否足够(即大于新生代中的所有存活对象大小),如下图,老年代目前是空的,1.5G的可用内存空间可以容纳Eden区中的1.2G对象,所以会直接进行Minor GC:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

那么, 此时Eden区中有多少对象还是存活的呢? 之前说了每次操作耗时10s,那么在1分钟内的最后10s时,前面0-50s的任务已经执行完了,1分钟操作100个任务,所以大约有1/6的任务还没有执行完毕,即大约还有20个任务在计算中(大约200MB对象存活):

其实线上一般是通过GC日志去分析存活对象的大小的,GC日志中清楚的记录了每次Minor GC进入到老年代的对象大小(后面我们会详细讲解如何看懂GC日志),根据我们的线上日志分析,大约也还有200MB对象是存活的。

注意,每一块Survivor区的大小只有100MB,所以是无法容纳200MB的存活对象的,所以会通过空间担保机制,转移到老年代中,并清空Eden区,此时JVM内存空间结构如下:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

由于每分钟老年代都被填充200MB存活对象,所以到第3分钟结束时,老年代已经有400MB空间被占满,且Eden区也被占满,此时如果要进行Minor GC,会怎么样呢?

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

首先,依然检查老年代的连续可用内存空间是否足够(即大于新生代中的所有存活对象大小),此时发现空间是不够的,老年代只有1.1GB可用,而新生代的所有对象大小有1.2GB。

此时,就会判断是否开启了空间担保机制——即判断HandlePromotionFailure是否为true,如果开启了(一般生产环境都会开启),就会看下历代晋升到老年代的对象大小是否小于老年代可用空间,根据之前的计算,历代晋升到老年代的对象大小约为200MB,小于1.1GB,所以JVM就会放心的进行一次Minor GC,此时又有200MB对象进入到老年代。

重复上述过程,大约经过8分钟,经历7次Minor GC后,JVM内存空间结构如下,此时老年代剩余可用空间大约100MB,Eden区已被占满:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

此时又会进行Minor GC前的检查,但是老年代的可用空间已经比历代晋升到老年代的对象空间小了,所以会 直接触发一次Full GC ,将老年代中的垃圾对象回收(假设此时老年代中的对象全部都可回收):

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

然后紧接着再进行一次Minor GC,将Eden区中的200MB存活对象转移到老年代:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

按照上述这个模型, 基本上8分钟左右就会触发一次Full GC ,这个频率对于生产环境是不可接受的,因为Full GC会严重影响系统性能,这个后面章节我们会详细讲解。

4.3 优化

那么该如何进行优化呢?最基本的思路就是 增加Survivor的内存大小 ,因为正是Survivor区不能容纳存活对象(200MB)导致必须晋升到老年代。所以重新分配新生代大小为2G,老年代为1G,同时改变Eden和Survivor的空间比例,这样Survivor区就能容纳每次Minor GC后的存活对象,如下图:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

比如经过一段时间,JVM内存结果如下,Eden区被占满,Survivor1区有200MB上一轮Minor GC后的存活对象:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

然后此时进行Minor GC,会先清理到S1区中的所有对象,然后将Eden区中的存活对象(200MB)转移到S2区:

JVM基础(5)——JVM垃圾回收算法,jvm专题,jvm

这样,基本上就很少会有对象进入到老年代,Full GC的频率能降低到几小时一次。

五、总结

最后来总结下本章的内容,本章主要介绍了新生代的复制算法和老年代的标记整理算法的流程,重点需要掌握的是以下几点:

  1. 新生代对象何时会进入老年代?
  2. 何时会触发新生代的Minor GC?
  3. 何时会触发FULL GC?
  4. 空间分配担保机制的作用是什么?

同时,本章也给出了一个线上示例,帮助读者更好的理解JVM分代垃圾回收的整个流程。下一章开始,我们将详细介绍各种垃圾回收器,看看它们内部是如何运用GC算法进行垃圾回收的。文章来源地址https://www.toymoban.com/news/detail-784039.html

到了这里,关于JVM基础(5)——JVM垃圾回收算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JVM---垃圾回收算法介绍

    目录 分代收集理论 三种垃圾回收算法 标记-清除算法(最基础的、基本不用) 标记-复制算法 标记-整理算法 正式因为jvm有了垃圾回收机制,作为java开发者不会去特备关注内存,不像C和C++。 优点 :开发门槛低、安全 缺点 :性能问题。c和c++可以自己操控内存等,性能更高

    2024年02月12日
    浏览(36)
  • 【JVM】垃圾回收算法

    标记-清除算法将垃圾回收分为两个阶段,标记阶段和清除阶段 在标记阶段首先通过GC Roots,标记所有从根节点开始的对象,未被标记的对象就是未引用的垃圾对象。然后,在清除阶段,清除未被标记的对象。 适合场景: 1、存活对象较多的情况下比较高效 2、使用于老年代

    2024年01月16日
    浏览(42)
  • JVM基础(6)——JVM垃圾回收器简介

    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖,挖的越深,基础越扎实! 阶段1、深入多线程 阶段2、深入多线程设计模式 阶段3、深入juc源码解析

    2024年01月23日
    浏览(41)
  • jvm垃圾回收相关的算法

    JVM主要通过以下几种方式来判断对象是否需要回收: 引用计数法:JVM通过引用计数器来判断对象的引用数量,当引用数量为0时,表示对象可以被回收。 可达性分析算法:JVM通过根对象(如栈中的引用、静态变量等)出发,对对象进行可达性分析,判断对象是否可被访问到,

    2024年02月02日
    浏览(60)
  • JVM之三大垃圾回收算法

    提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 复制算法的核心就是, 将原有的内存空

    2024年02月14日
    浏览(53)
  • 【JVM篇】有哪些垃圾回收算法

    主要分为下面2个阶段 标记阶段,将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出所有存活对象 清除阶段,从内存中删除没有被标记的对象(非存活对象) 优点:实现简单,只需要在第一阶段给每个对象维护标志位,第二阶段删除对象

    2024年02月21日
    浏览(35)
  • JVM垃圾回收算法和CMS垃圾收集器

    目录 判断一个对象是否死亡? 1、引用计数法  2、可达性分析算法 三色标记 垃圾收集算法 1、分代收集理论  2、垃圾回收算法 标记-清除 标记-复制 标记-整理 CMS(Concurrent Mark Sweep)收集器 CMS垃圾收集器步骤 CMS垃圾收集器优缺点 垃圾收集需要完成的三件事情: 哪些内存需

    2024年02月10日
    浏览(47)
  • JVM-垃圾回收(标记算法,收集器)

    申明:文章内容是本人学习极客时间课程所写,文字和图片基本来源于课程资料,在某些地方会插入一点自己的理解,未用于商业用途,侵删。 原资料地址:课程资料 垃圾回收的基本原理 1 什么是垃圾? 在内存中,没有被引用的对象就是垃圾。 2 如果找到垃圾对象? 引用计

    2024年02月21日
    浏览(54)
  • 说一下 JVM 有哪些垃圾回收算法?

    标记无用对象,然后进行清除回收。 标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段: 标记阶段:标记出可以回收的对象。 清除阶段:回收被标记的对象所占用的空间。 标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法

    2024年02月22日
    浏览(43)
  • 3.Java面试题—JVM基础、内存管理、垃圾回收、JVM 调优

    一篇文章掌握整个JVM,JVM超详细解析!!! JVM (Java虚拟机) 是运行 Java 字节码 的 虚拟机 。 JVM 针对 不同系统 有 特定实现 ( Windows 、 Linux 等),目的是 同样的代码 在 不同平台 能运行出 相同的结果 。 Java 语言 要经过 编译 和 解释 两个步骤: 编译 :通过 编译器 将 代码 一

    2024年02月15日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包