zoukankan      html  css  js  c++  java
  • 垃圾回收(GC)的三种基本方式

    垃圾回收(GC)的三种基本方式

      垃圾:就是程序需要回收的对象,如果一个对象不在被直接或者间接地引用,那么这个对象就成为了垃圾,它占用的内存需要及时地释放,否则就会引起内存泄漏。

      这里可以大致的分为两类:跟踪回收,引用计数。

      垃圾回收统一理论一文阐述了一个理论:任何垃圾回收的思路,无非以上两种的组合,其中一种的改善和进步,必然伴随着另一种的改善和进步。

    跟踪回收:

      跟踪回收的方式独立于程序,定期运行来检查垃圾,需要较长时间的中断。

    标记清除:

      标记清除的方式需要对程序的对象进行两次扫描,第一次从根(root)开始扫描,被根引用了的对象标记为不是垃圾,不是垃圾的对象引用的对象同样标记为不是垃圾,以此递归。所有不是垃圾的对象的引用都扫描完了之后。就进行第二次扫描,第一次扫描中没有得到标记的对象就是垃圾了,对此进行回收、

     复制收集

      复制收集的方式只需要对对象进行一次扫描。准备一个新的空间,从根开始,对对象进行扫描,如果存在对这个对象的引用,就把它复制到新空间中,一次扫描结束之后,所有存在于新空间的对象就是所有的非垃圾对象。

      这两种方式各有千秋,标记清除的方式节省内存但是两次扫描需要更多的时间,对于垃圾比较小的情况占由优势。复制收集更快速但是需要额外开辟一块用来复制的内存,对垃圾比例较大的情况占优势。特别的,复制收集由局部性的优点。

      在复制收集的过程中,会按照对象被引用的顺序将对戏那个复制到新空间中,于是,关系比较近的对象被放在距离较近的内存空间的可能性会提高,这叫做局部性。局部性高的情况下,内存缓存会更有效地运作,程序的性能会提高。

    对于标记清除,有一种标记-压缩算法的衍生算法:

    对于压缩阶段,它的工作就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来大刀减少内存碎片的目的。

    引用计数

    引用计数是指,针对每个对象,保存一个对该对象的引用计数,该对象的引用增加,则相应的引用计数增加,如果该对象的引用计数为0,则回收该对象。

    优点:

    • 引用计数最大的优点就是容易实现,C++程序员应该都实现过类似的机制。
    • 二是成本小,基本上引用计数为0的时候垃圾会被立即回收,而其他方法难以预测对象的生命周期,垃圾存在的时间都会比这个方法高
    • 这种垃圾回收方式产生的中断时间短。

    缺点:

    • 最著名的缺点就是如果对象中存在循环引用,就无法被回收。例如:下面三个对象互相引用,但是不存在从根(Root)指向的引用,所以已经是垃圾了。但是引用计数不为0.
    • 引用计数不适合在并行中使用,多个线程同时操作引用计数,会引起数值不一样的问题,从而导致内存错误。所以引用计数必须采用独占方式,如果引用操作频繁,那么加锁等并发控制机制的开销是相当大的。

    Perl和Python采用了这种GC机制。

    他们的衍生算法

    分代回收:

    他们回收方式用了程序的一种特性:大部分对象会从产生开始在很短的时间内变成垃圾,而存在的很长时间的对象往往都有较长的生命周期。高频对新生成的对象进行回收,称为小回收,低频对所有对象回收,称为大回收。每一次小回收过后,就把存活下来的对象归位老生代,小回收的时候,遇到老生代直接跳过去,大多数分代回收算法都采用的复制收集方法,因为小回收中垃圾的比例较大。

    这种方式存在一个问题:如果在某个新生代的对象中,存在老生代的对象对它的引用,他就不是垃圾了,那么怎么制止小回收对其 回收呢?这里用到了一种叫做写屏障的方式?

    程序对所有涉及修改对象内容的地方进行保护,被称为写屏障(writer barrier),写屏障不仅用于分代回收,也用于其他GC算法中。

    在此算法的表现是,用一个记录集来记录从新生代到老生代的 引用。如果由两个对象A和B,当对A的对象内容进行修改并加入B的引用时,如果A时老生代B时新生代,则将这个引用加入到记录集中。小回收的时候,因为记录集中由对B的引用,所以B不是垃圾。

    增量回收

    上面的算法缩短了GC平均中断时间,但是在对实时性要求很高的程序中,对GC最高中断时间的要求更高。比如,自动驾驶软件,如果某次GC中断了0.1S,那么损失可能就是致命的。

    增量回收就是将GC分成几部分来执行。设置GC最多中断10ms这样的条件限制来使GC的中断时间视作使可预测的。

    但是,在两段的GC程序之间,引用关系可能发生了变化。所以,这种GC算法也要写屏障,来记录引用关系的变化。虽然这种方式控制了中断最高时间,但是由于中断次数增加,GC总时间是增加的。

    并行回收

    基本原理是,在程序运行的同时进行GC工作,最大化CPU的性能。但是这种方式也要面对增量回收的问题。所以要进行写屏障操作。

    然而这种方式也并未做到完全不暂停原程序的运行,在某些特定的GC阶段还是要暂定源程序。多核化迅速发展的今天,这种苏昂法也在不断优化。不间断原程序实现并行回收这个领域是相当值得期待的。

  • 相关阅读:
    PVS 7.6 部署教程
    PHP下载远程图片的3个方法
    [Xcode 实际操作]二、视图与手势-(2)UIView视图的层次关系
    [Swift]检查API可用性
    [Xcode 实际操作]二、视图与手势-(1)UIView视图的基本使用
    [Swift]LeetCode103. 二叉树的锯齿形层次遍历 | Binary Tree Zigzag Level Order Traversal
    [Swift]forEach详解
    [Swift]LeetCode937. 重新排列日志文件 | Reorder Log Files
    [Swift]LeetCode940. 不同的子序列 II | Distinct Subsequences II
    [Swift]LeetCode939. 最小面积矩形 | Minimum Area Rectangle
  • 原文地址:https://www.cnblogs.com/qingmuchuanqi48/p/13749652.html
Copyright © 2011-2022 走看看