JVM--16---垃圾回收相关算法 2----清除阶段算法

Posted 高高for 循环

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM--16---垃圾回收相关算法 2----清除阶段算法相关的知识,希望对你有一定的参考价值。


垃圾回收相关算法

  • 标记阶段—-引用计数算法
  • 标记阶段—-可达性分析算法

  • 清除阶段—-标记-清除算法
  • 清除阶段—-复制算法
  • 清除阶段—-标记-整理算法

  • 分代收集算法
  • 增量收集算法
  • 分区算法

1. 清除阶段—标记-清除算法

当成功区分出内存中存活对象和死亡对象后,GC 接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。

背景

  • 标记-清除算法(Mark-Sweep)是一种非常基础和常见的垃圾收集算法,该算法被 J.McCarthy 等人在1960年提出并并应用于 Lisp 语言。

执行过程

当堆中的有效内存空间(Available Memory)被耗尽的时候,就会停止整个程序(也被称为Stop The World),然后进行两项工作,第一项则是标记,第二项则是清除

标记的是引用的对象,不是垃圾!!

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

什么是清除?

这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放覆盖原有的地址

标记-清除算法—缺点:

  1. 标记清除算法的效率不算高
  2. 在进行 GC 的时候,需要停止整个应用程序(Stop The World),导致用户体验较差
  3. 这种方式清理出来的空闲内存是不连续的,产生内存碎片,需要维护一个空闲列表

空闲列表

关于空闲列表是在为对象分配内存的时候

  • 如果内存规整:
    采用指针碰撞的方式进行内存分配。
  • 如果内存不规整
    虚拟机需要维护一个列表。空闲列表分配。

2. 清除阶段—复制算法

背景

  • 为了解决标记-清除算法在垃圾收集效率方面的缺陷,M.L.Minsky 于1963年发表了著名的论文,“使用双存储区的 Lisp 语言垃圾收集器 CA LISP Garbage Collector Algorithm Using Serial Secondary Storage)”。M.L.Minsky 在该论文中描述的算法被人们称为复制(Copying)算法,它也被 M.L.Minsky本人成功地引入到了Lisp 语言的一个实现版本中。

核心思想

  1. 将活着的内存空间分为两块,每次只使用其中一块,
  2. 在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中
  3. 后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收


把可达的对象,直接复制到另外一个区域中复制完成后,A 区就没有用了,里面的对象可以直接清除掉,

新生代 幸存者区里面就用到了复制算法

优点

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

缺点

  • 此算法的缺点也是很明显的,就是需要两倍的内存空间
  • 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region
    之间对象引用关系,不管是内存占用或者时间开销也不小

注意

  • 如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,或者说非常低才行(老年代大量的对象存活,那么复制的对象将会有很多,效率会很低),特别适合垃圾对象很多,存活对象很少的场景;例如:Young 区的 Survivor0 和 Survivor1 区

应用场景

Young 区的 Survivor0 和 Survivor1 区

在新生代,对常规应用的垃圾回收,一次通常可以回收 70% - 99% 的内存空间。回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。

3. 清除阶段—标记-压缩(整理)算法

背景

执行过程

  • 第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象
  • 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。

标清和标整的区别

  • 标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法
  • 二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM
    只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。

指针碰撞(Bump the Pointer)

  • 如果内存空间以规整和有序的方式分布,即已用和未用的内存都各自一边,彼此之间维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,只需要通过修改指针的偏移量将新对象分配在第一个空闲内存位置上,这种分配方式就叫做指针碰撞(Bump the Pointer)

优点

  • 消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可。
  • 消除了复制算法当中,内存减半的高额代价。

缺点

  • 从效率上来说,标记-整理算法要低于复制算法。
  • 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址
  • 移动过程中,需要全程暂停用户应用程序。即:STW

4. 分代收集算法

小结

  • 效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存
  • 而为了尽量兼顾上面提到的三个指标,标记-整理算法相对来说更平滑一些,但是效率上不尽如人意,它比复制算法多了一个标记的阶段,比标记-清除多了一个整理内存的阶段。

分代收集算法–概念

前面所有这些算法中,并没有一种算法可以完全替代其他算法,它们都具有自己独特的优势和特点。分代收集算法应运而生。

  • 分代收集算法,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。一般是把ava 堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法,以提高垃圾回收的效率。
  • 在 Java 程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如 Http 请求中的 Session对象、线程、Socket 连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

目前几乎所有的 GC 都采用分代收集算法执行垃圾回收的

HotSpot 分代收集算法

在 HotSpot 中,基于分代的概念,GC 所使用的内存回收算法必须结合年轻代和老年代各自的特点。

年轻代(Young Gen)

复制算法

  • 年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。
  • 这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过HotSpot 中的两个 Survivor 的设计得到缓解。

老年代(Tenured Gen)

由标记-清除或者是标记-清除与标记-整理的混合实现

老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。

这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与标记-整理的混合实现。

  • Mark 阶段的开销与存活对象的数量成正比。
  • Sweep 阶段的开销与所管理区域的大小成正相关。
  • Compact 阶段的开销与存活对象的数据成正比。

HotSpot中 CMS 回收器、Serial Old 回收器

  • 以 HotSpot 中的 CMS 回收器为例,CMS 是基于 标记-清除 Mark-Sweep 实现的,对于对象的回收效率很高。而对于碎片问题,
  • CMS 采用基于 标记-整理 Mark-Compact 算法的 Serial Old回收器作为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure 时),将采用 Serial Old 执行 Full GC 以达到对老年代内存的整理。

分代的思想被现有的虚拟机广泛使用。几乎所有的垃圾回收器都区分新生代和老年代

5. 增量收集算法

基本思想

  • 让垃圾收集线程和应用程序线程交替执行

  • 垃圾收集线程以分阶段的方式完成标记、清理或复制工作

缺点

6. 分区算法

一般来说,在相同条件下,堆空间越大,一次 GC 时所需要的时间就越长,有关 GC 产生的停顿也越长。为了更好地控制 GC 产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次 GC 所产生的停顿。

  • 分代算法将按照对象的生命周期长短划分成两个部分,
  • 分区算法将整个堆空间划分成连续的不同小区间。

实现原理:

  • 分区算法将整个堆空间划分成连续的不同小区间。 每一个小区间都独立使用,独立回收。

好处:

  • 这种算法的好处是可以控制一次回收多少个小区间

写到最后

  • 注意,这些只是基本的算法思路,实际 GC 实现过程要复杂的多,目前还在发展中的前沿 GC 都是复合算法,并且并行和并发兼备。

以上是关于JVM--16---垃圾回收相关算法 2----清除阶段算法的主要内容,如果未能解决你的问题,请参考以下文章

JVM学习笔记GC——JAVA预言的垃圾回收

浅谈JVM垃圾回收器相关知识点

浅谈JVM垃圾回收器相关知识点

JVM之垃圾收集算法与垃圾收集器

垃圾搜集算法

带你整理面试过程中关于 JVM 的运行内存划分垃圾回收算法和 4种引用类型的相关知识点