栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

垃圾收集算法与垃圾收集理论

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

垃圾收集算法与垃圾收集理论

垃圾收集算法

​ 从如何判定对象消亡的角度出发,垃圾收集算法可以划分为“引用计数式垃圾收集”(Reference Counting GC)和“追踪式垃圾收集”(Tracing GC)两大类,这两类也常被称作 “直接垃圾收集”和“间接垃圾收集”。由于引用计数式垃圾收集算法在主流Java虚拟机中均未涉及,所以后续文章的理论都是基于“追踪式垃圾收集”(Tracing GC)实现的。

分代收集理论

​ 当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”(Generational Collection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分 代假说之上:

  1. 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
  2. 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
  3. 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极
    少数。

​ 前两种假说共同组成了大多数常用垃圾收集器的设计原则: 将Java堆划分成不同的几个区域,根据对象的不同年龄(经历过垃圾收集的次数)将对象分放到不同的区域。如果一些对象是朝生夕灭的,那么这些对象应该放到同一个区域内进行管理, 每次回收都只需要关注能够存活下来的少量对象,也就是只标记会存活下来的对象(这也是为什么可达性分析算法标记的是存活对象的原因之一),而长久不灭或者生命周期很长的对象就把它们集中放在一块, 虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用

​ 根据对象生命周期不同将Java虚拟机的内存划分成不同的几个区域: 新生代(Eden 区), 老年代(Old 区) 从而有了“Minor GC”, “Major GC”, “Full GC”这样的回收类型的划分。从新生代经历GC存活下来的对象会逐步放入到老年代中存放。

​ 但是分代的收集不仅仅只是分代就能解决所有事情,有一种情况就是对象之间可能存在跨代引用,比如说 要判断一个要判断一个新生代是否需要被回收,就要判断是否有别的对象到该对象的引用;但是可能会有老年代的对象引用了该对象,那么就不能单单判断新生代的GC Roots,还要跨代额外判断老年代的GC Roots。 这样会对内存造成很大的负担。

所以就需要第三条假说 ------ 跨代引用假说(Intergenerational Reference Hypothesis)

​ 这其实是可根据前两条假说逻辑推理得出的隐含推论: 存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。 举个例子,如果某个新生代对象存在跨代引用,由于老年代对象难以 消亡,该引用会使得新生代对象在收集时同样得以存活,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了。

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

几种不同的收集描述:

  • 部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:
    • 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
    • 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
    • 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
  • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
标记-清除算法

​ 最早出现也是最基础的垃圾收集算法。标记-清除算法和它的名字一样,就是有两个阶段,一个阶段进行标记,一个阶段进行清除操作。当有效的内存资源耗尽时就会进入STW (Stop The World) 状态,然后进行标记-清除。

  1. 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。(注意:一般来讲,标记的是被引用的对象,也就是可达对象,并非标记的是即将被清除的垃圾对象)
  2. 清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收

缺点:

  1. 执行效率不稳定,如果Java堆中包含大量对 象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低。
  2. 存在内存空间的碎片化问题,标记、清除之后会产生大 量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记-复制算法

标记-复制算法常被简称为复制算法。为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,1969年Fenichel提出了一种称为**“半区复制”(Semispace Copying)**的垃圾收集算法, 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。 当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉(就像是幸存者0区和幸存者1区)。

优点:

  1. 没有标记和清除过程,实现简单,运行高效
  2. 复制过去以后保证空间的连续性,不会出现“碎片”问题。

缺点:

  1. 需要两倍的内存空间。
  2. 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小

​ 在1989年,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“Appel式回收”。HotSpot虚拟机的Serial、ParNew等新生代收集器均采用了这种策略来设 计新生代的内存布局。Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍 然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即 每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会 被“浪费”的。 当然,98%的对象可被回收仅仅是“普通场景”下测得的数据,任何人都没有办法百分百 保证每次回收都只有不多于10%的对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”的安 全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保(Handle Promotion)。

标记-整理算法

针对老年代的存放特征,1974年Edward Lueders提出了另外一种有针对性的“标记-整理”(Mark-Compact)算法,标记时与标记-清除算法一样进行标记,但是在整理阶段是将对象都移动到内存的一端,然后清理其它内存区域。

标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。

在老年代这种每次回收都有大量对象存活的区域来讲,如果要移动存活对象并且将对象的相关引用更新是一项繁杂的操作,所以必须要暂停用户线程的工作,全身心投入到垃圾清理工作中,这样就会造成用户线程停顿,这样的停顿就叫做STW(Stop The World)。

增量收集算法

在前面的算法里,进行垃圾收集的时候不得不进入Stop The World状态。在Stop The World期间,所有的应用线程都会被挂起,这样显得有些卡顿。所以,为了解决这种问题,增量收集(Incremental Collecting)算法诞生了。

增量收集算法的基本思想:

  1. 如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。垃圾收集线程每次只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。
  2. 总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作

优点:

可以减少系统的暂停时间。

缺点:

在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

分区算法
  • 般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就越长,有关GC产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间(region),而不是整个堆空间,从而减少一次GC所产生的停顿。
  • 分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
  • G1垃圾回收器就使用了这个算法。
总结

所有的算法都是思路,实际的实施和实际情况可能更加复杂,所以很多的GC都是中和这些算法的优缺点的复合算法。没有最优的算法,只有合适的算法。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/876525.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号