垃圾收集算法的实现涉及大量的程序细节,且各个平台的虚拟机操作内存的方法都有差异,本文暂不过多讨论算法实现,只重点介绍分代收集理论和几种算法思想及其发展过程。

1、分代收集理论

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”的理论进行设计,它建立在两个分代假说之上:

  • 弱分代假说:绝大多数对象都是朝生夕灭的

  • 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡

这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:

  • 收集器应该将Java堆划分出不同的区域

  • 然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储

  • 如果一个区域中大多数对象都是朝生夕灭,那么把它们集中放在一起,就能以较低代价回收到大量的空间

  • 如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域

  • 这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用

在Java堆划分出不同的区域之后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域,因而才有了

  • Minor GC

  • Major GC

  • Full GC

这样的回收类型的划分;也才能够针对不同的区域使用相匹配的垃圾收集算法,因而发展出了

  • 标记-复制算法

  • 标记-清除算法

  • 标记-整理算法

等针对性的垃圾收集算法。

1.1、分代假说存在什么问题?

但是分代收集并非只是简单划分一下内存区域那么容易,它至少存在一个明显的困难:对象不是孤立的,对象之间会存在跨代引用。

假如现在只进行一次新生代收集(Minor GC),但新生代中的对象是完全有可能被老年代所引用的,为了找出该区域中的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性,反过来也是一样。

遍历整个老年代所有对象的方案虽然理论上可行,但无疑会为内存回收带来很大的性能负担,为了解决这个问题,就需要对分代收集理论添加第三条经验法则:

  • 跨代引用假说:跨代引用相对于同代引用来说仅占极少数。

1.2、如何解决?

存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。举个例子,如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生代对象在收集时同样难以消亡,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了。

因此,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称为记忆集”),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。虽然这种方法需要在对象改变引用关系时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。

部分收集(Partial GC)又分为:

  • 新生代收集(Minor GC/Young GC):只收集新生代

  • 老年代收集(Major GC/Old GC):只收集老年代。目前只有CMS收集器会有单独收集老年代的行为,Major GC这个说法现在有点混淆,需按上下文区分到底是指老年代的收集还是整堆收集

混合收集(Mixed GC):收集整个新生代以及部分老年代,目前只有G1收集器会有这种行为

整堆收集(Full GC):收集整个Java堆和方法区

2、标记清除法

最早出现也是最基础的垃圾收集算法是标记-清除(Mark-Sweep)算法,分为“标记”和“清除”两个阶段:

  • 首先标记出所有需要回收的对象

  • 在标记完成后,统一回收掉所有被标记的对象

  • 也可以反过来,标记存活的对象,统一回收所有未被标记的对象

标记过程就是对象是否属于垃圾的判定过程。

之所以说它是最基础的收集算法,是因为后续的收集算法大多都是以标记-清除算法为基础,对其缺点进行改进而得到的。它的主要缺点有两个:

  • 执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低

  • 内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片

3、标记复制法

3.1、标记复制法的思想和优缺点

为了解决标记清除算法面对大量可回收对象时执行效率低的问题,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销,但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。这样实现简单,运行高效。

其缺陷也显而易见,这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费未免太多了一点。

3.2、HotSpot虚拟机的具体实现

HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%)。当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保,这些对象便将通过分配担保机制直接进入老年代,这对虚拟机来说就是安全的。

3.3、标记复制法的具体步骤

  1. 首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活着的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄 + 1

  2. 然后,情况Eden和SurvivoFrom中的对象,也即复制之后有交换,谁空谁是To

  3. SurvivorTo和SurvivorFrom互换

  4. 最后,SurvivoTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区。部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入老年代

4、标记整理法

4.1、标记清除法的思想

标记复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

针对老年代对象的存亡特征,标记整理法其中的标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存,如下图所示。

4.2、标记整理法的优缺点

标记清除算法与标记整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策:

  • 如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行,这就更加让使用者不得不小心翼翼地权衡其弊端了。

  • 但如果跟标记清除算法那样完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。内存的访问是用户程序最频繁的操作,甚至都没有之一,假如在这个环节上增加了额外的负担,势必会直接影响应用程序的吞吐量。

基于以上两点,是否移动对象都存在弊端。

  • 从垃圾收集的停顿时间来看,不移动对象停顿时间会更短,甚至可以不需要停顿

  • 但是从整个程序的吞吐量来看,移动对象会更划算

HotSpot虚拟机里面关注吞吐量的Parallel Scavenge收集器是基于标记整理算法的,而关注延迟的CMS收集器则是基于标记清除算法的。另外还有一种“和稀泥式”解决方案,做法是让虚拟机平时多数时间都采用标记清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用一次标记整理法,前面提到的基于标记清除法的CMS收集器面临空间碎片过多时采用的就是这种处理办法。

标记复制法、标记清除法和标记整理法的区别相关推荐

  1. (小白)函数一: 声明函数的方法—语句定义法和表达式定义法的区别

    一.函数的定义: 在说明什么是函数前先举一个小例子: 大家都知道印刷术是我国的四大发明(科普一下:中国四大发明:造纸术.印刷术.火药.指南针)之一,之所以有印刷术,是因为重复的抄写既浪费时间,效率又很 ...

  2. 浅谈排序算法:冒泡排序法和选择排序法的区别

    之前学习了冒泡排序法和选择排序法,最近被老师问某个道题用的是什么排序法.自己居然答不出来,才发现自己没有真正弄懂,这两个算法的原理和区别,所以····· 1冒泡排序法 1.1什么是冒泡排序法? 顾名思 ...

  3. 小驼峰法和大驼峰法的区别

    小驼峰法: 变量一般用小驼峰法标识.驼峰法的意思是:除第一个单词之外,其他单词首字母大写.如 helloWorld; 变量HelloWorld第一个单词是全部小写,后面的单词首字母大写. 常用于函数名 ...

  4. 三相电机△接法和Y接法的区别

    1.Y接法 1 绕组承受电压220V  2.△接法      1 绕组承受电压380V 星形接法由于起输出功率小,常用于小功率,大扭矩电机,或功率较大的电机起步时候用,这样对机器损耗较小,正常工作后再 ...

  5. 头插法和尾插法的详细区别

    浅析线性表(链表)的头插法和尾插法的区别及优缺点 线性表作为数据结构中比较重要的一种,具有操作效率高.内存利用率高.结构简单.使用方便等特点,今天我们一起交流一下单向线性表的头插法和尾插法的区别及优缺 ...

  6. 67.Java垃圾收集机制\对象引用\垃圾对象的判定\垃圾收集算法\标记—清除算法\标记—整理算法\分代收集\垃圾收集器\性能调优

    67.Java垃圾收集机制 67.1.对象引用 67.2.垃圾对象的判定 67.3.垃圾收集算法 67.3.1.标记-清除算法 67.3.2.标记-整理算法 67.3.3.分代收集 67.4.垃圾收集 ...

  7. 垃圾回收之垃圾回收算法(标记清除、标记整理、复制)、分代垃圾回收

    2. 垃圾回收算法 之前我们学习了如何判断一个对象是不是可以作为垃圾被回收,但是具体回收还需要依赖一些回收方面的算法,常见的有三种: 分别是标记清除.标记整理.和复制这三种算法,下面先从第一种标记清除 ...

  8. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,优化收集方法的思路

    面试java中,jvm中的垃圾收集算法一般都会问一下,下面来总结一下这个经常提到的问题. 第一种:标记清除 它是最基础的收集算法. 原理:分为标记和清除两个阶段:首先标记出所有的需要回收的对象,在标记 ...

  9. c# 清除 html,C#方法清除HTML标记

    您的位置:首页 - 教程 - C# - 正文 C#方法清除HTML标记 #region 清除HTML标记 public static string DropHTML(string Htmlstring ...

最新文章

  1. 面了大厂的 Python 岗后,炸了!
  2. 【BZOJ】1711: [Usaco2007 Open]Dining吃饭
  3. 近期活动盘点:清华大学海外学者短期讲学:创新创业发展课程
  4. 阿里云盾技术强在哪里?轻松防御DDoS、CC攻击
  5. Eclipse自动代码补全
  6. 如何快速压测电商网站?
  7. 图论--最短路--SPFA
  8. 新iPhone将采用更大容量电池:最低3110mAh?
  9. 1088 三人行 (20分)
  10. 分布式定时任务——elastic-job
  11. Eclipse内存溢出
  12. 【scrapy】scrapy按分类爬取豆瓣电影基础信息
  13. Hadoop 1.x 和 Hadoop 2.x 有什么区别?
  14. 银行职业性格测试软件,2018银行春招:不要小看银行性格测试
  15. 袋鼠云 oracle,袋鼠云数据中台专栏2.0 | 数据中台之数据集成
  16. 计算机的基本键盘知识,知识:计算机键盘上每个键的功能_计算机的基本知识_IT /计算机_信息...
  17. 线性代数常用基本知识整理
  18. 北京工商大学计算机考研818真题,北京工商大学考研真题汇总
  19. java中string类型如何接收_java中的string类型全面解析
  20. 数据库发展史(引用1)

热门文章

  1. 什么是Apple Music 杜比全景声?如何设置开启?
  2. 软件工程的先驱 女软件工程师Margaret Hamilton
  3. 学习Altium Designer软件总结
  4. 解决:Windows照片查看器无法显示此图片,因为计算机上的可用内存可能不足的问题
  5. 计算机应用基础网络统考操作,(全套)知识点练习-计算机应用基础-(网络统考-操作题-共152题)...
  6. 程序NSIS ERROR出错解决方案
  7. 华为手机充满有提醒吗_手机充满电铃声提醒软件下载-华为手机充满电铃声提醒软件app下载 v1.0-友情手机站...
  8. Openlayers 图形要素 Feature 移动和编辑
  9. QT 文本html显示格式的问题,如在QTextBrowser.setText用tr(),其中为html格式
  10. Spring Cloud 微服务开放平台接口