垃圾回收机制

引用计数回收器(Reference Counting Collector)

  原理是在每个对象内部维护一个整数值,叫做这个对象的引用计数,当对象被引用时引用计数加一,当对象不被引用时引用计数减一。当引用计数为 0 时,自动销毁对象。

目前引用计数法主要用在 c++ 标准库的 std::shared_ptr 、微软的 COM 、Objective-C 和 PHP 中。

  计数器表示资源(内存中的对象)的使用次数,当计数器变成零的时候就可以将该资源销毁。在向已有的系统添加垃圾回收器时,开发人员通常会选择计数回收器,因为这种方式最容易与已有的资源管理器和代码集成在一起

  不过引用计数算法存在很多问题。最大的一个问题是,它无法解决循环引用问题。

循环引用是指对象 A 和对象 B 互相持有对方的引用。这样两个对象的引用计数都不是 0 ,因此永远不能被收集,造成内存泄露。除此之外,引用计数需要额外的开销。

  虽然现代CPU的运算能力很强,但内存并不快,而计数器的运算需要频繁使用内存。因为计数器需要不断被更新,所以它们不是只读的,而且不能保证线程安全。
  引用计数器算法是一种摊销算法(将开销分摊给了程序),不过它是偶然性的摊销,无法保证响应时间。例如,假设程序正在处理一个很大的树结构,最后一个使用这个树的程序会触发销毁操作,根据墨菲定律,其延迟会比期望的要高。

标记清除回收器(Mark Sweep Collector)

  标记清除回收算法解决了一些在引用计数算法中存在的问题。它解决了循环引用问题,而且开销要小得多,因为它不需要维护计数器。
  这个算法分为两步,标记和清除。
    1. 标记:从程序的根节点开始, 递归地 遍历所有对象,将能遍历到的对象打上标记。
    2. 清除:讲所有未标记的的对象当作垃圾销毁。


  不过,它无法立即检测到垃圾。这个算法还有一个缺陷,就是人们常常说的 STW 问题(Stop The World)。因为算法在标记时必须暂停整个程序,否则其他线程的代码可能会改变对象状态,从而可能把不应该回收的对象当做垃圾收集掉。
  标记清除算法有更高的一致性要求,而且难以将其集成到已有的系统中。在标记阶段,回收器要求遍历所有的存活对象,包括封装在对象里的数据。如果某个对象不支持回收器的遍历访问,那么使用这种回收器就会存在风险。

复制回收器(Copying Collector)

  节点复制也是基于追踪的算法。其将整个堆等分为两个半区(semi-space),一个包含现有数据,另一个包含已被废弃的数据。节点复制式垃圾收集从切换(flip)两个半区的角色开始,然后收集器在老的半区,也就是 Fromspace 中遍历存活的数据结构,在第一次访问某个单元时把它复制到新半区,也就是 Tospace 中去。在 Fromspace 中所有存活单元都被访问过之后,收集器在 Tospace 中建立一个存活数据结构的副本,用户程序可以重新开始运行了。

优点

  1. 所有存活的数据结构都缩并地排列在 Tospace 的底部,这样就不会存在内存碎片的问题。
  2. 获取新内存可以简单地通过递增自由空间指针来实现。

缺点

  1. 内存得不到充分利用,总有一半的内存空间处于浪费状态。
  2. Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。

标记整理回收器(Mark Compact Collector)

  为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
  标记整理算法清理内存的方式不是通过清除,而是将对象移动到空余的内存空间。对象总是以不变的次序存留在内存里,先分配到内存的对象总处于较低的内存段,不过因为经过移动,对象间的内存间隙被消除。

  新创建的对象总是处于内存的高段。这种内存分配器被称为“bump”,类似于栈的分配,只是没有栈那样的大小限制。有些使用bump分配器的系统甚至不用调用栈来存储数据,它们直接在堆里分配调用帧,并把它们看成对象。

  从理论上看,这种回收算法的另一个优势在于,程序具有了更好的内存访问模型,这种模型对现代硬件的内存缓存更加友好。不过,相比其他算法,我们很难直观地感受到这种优势,因为引用计数和标记清除算法里所使用的内存分配器虽然很复杂,但它们很健壮,很高效。

  标记整理是一种复杂的算法,需要多次遍历所有分配到内存的对象。这种复杂性所带来的主要好处就是极低的内存开销。Oracle的Hotspot虚拟机使用了多种垃圾回收算法。

三色标记法

三色标记算法是对标记阶段的改进,原理如下:
  1. 起初所有对象都是白色。
  2. 从根出发扫描所有可达对象,标记为灰色,放入待处理队列。
  3. 从队列取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。
  4. 重复 3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。

  这个算法可以实现 "on-the-fly",也就是在程序执行的同时进行收集,并不需要暂停整个程序。
但是也会有一个缺陷,可能程序中的垃圾产生的速度会大于垃圾收集的速度,这样会导致程序中的垃圾越来越多无法被收集掉。

分代收集

  分代收集也是传统 Mark-Sweep 的一个改进。这个算法是基于一个经验:绝大多数对象的生命周期都很短。所以按照对象的生命周期长短来进行分代。
  一般 GC 都会分三代,在 java 中称之为新生代(Young Generation)、年老代(Tenured Generation)和永久代(Permanent Generation);在 .NET 中称之为第 0 代、第 1 代和第2代。
  原理如下:
    1. 新对象放入第 0 代
    2. 当内存用量超过一个较小的阈值时,触发 0 代收集
    3. 第 0 代幸存的对象(未被收集)放入第 1 代
    4. 只有当内存用量超过一个较高的阈值时,才会触发 1 代收集
    5. 2 代同理
  因为 0 代中的对象十分少,所以每次收集时遍历都会非常快(比 1 代收集快几个数量级)。只有内存消耗过于大的时候才会触发较慢的 1 代和 2 代收集。

因此,分代收集是目前比较好的垃圾回收方式。使用的语言(平台)有 jvm、.NET 。

  基于追踪的垃圾回收算法(标记-清扫、节点复制)一个主要问题是在生命周期较长的对象上浪费时间(长生命周期的对象是不需要频繁扫描的)。同时,内存分配存在这么一个事实 “most object die young”。基于这两点,分代垃圾回收算法将对象按生命周期长短存放到堆上的两个(或者更多)区域,这些区域就是分代(generation)。对于新生代的区域的垃圾回收频率要明显高于老年代区域
  分配对象的时候从新生代里面分配,如果后面发现对象的生命周期较长,则将其移到老年代,这个过程叫做 promote。随着不断 promote,最后新生代的大小在整个堆的占用比例不会特别大。收集的时候集中主要精力在新生代就会相对来说效率更高,STW 时间也会更短。
  目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

  而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
  注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

优点

性能更优。

缺点

实现复杂

http://www.frankyang.cn/2017/08/30/gc/

几种垃圾回收GC概述的更多相关文章

  1. 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配

    垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  2. Java 垃圾回收(GC) 泛读

    Java 垃圾回收(GC) 泛读 文章地址:https://segmentfault.com/a/1190000008922319 0. 序言 带着问题去看待 垃圾回收(GC) 会比较好,一般来说主要 ...

  3. JVM垃圾回收机制概述

    JVM垃圾回收机制概述 1.定义 是指JVM用于释放那些不再使用的对象所占用的内存. 2.方式 2.1引用计数(早期) 当引用程序创建引用以及引用超出范围时,JVM必须适当增减引用数.当某个对象的引用 ...

  4. [JVM-3]Java垃圾回收(GC)机制和垃圾收集器选择

    哪些内存需要回收? 1.引用计数法 这个算法的实现是,给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1:当引用失效时,计数器值-1.任何时刻计数值为0的对象就是不可能再被使用的.这 ...

  5. 性能测试三十五:jvm垃圾回收-GC

    垃圾回收-GC 三个问题 哪些内存需要回收? 什么时候回收? 如何回收? YoungGC和FullGC: 新生代引发的GC叫YoungGC 老年代引发的GC叫FullGC FullGC会引起整个Jvm ...

  6. golang 垃圾回收 gc

    http://ruizeng.net/golang-gc-internals/ 摘要 在实际使用go语言的过程中,碰到了一些看似奇怪的内存占用现象,于是决定对go语言的垃圾回收模型进行一些研究.本文对 ...

  7. 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法

    垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  8. 垃圾回收GC

    ​ 每种语言都有自己的垃圾回收机制.接下来我们来讲一下python的垃圾回收机制. 小整数对象池:python对小整数的定义为[-5,257),这些整数对象是提前建立好的,不会被垃圾回收.单个字母也一 ...

  9. JVM学习——垃圾回收GC(学习过程)

    JVM学习-垃圾回收(GC) 2020年02月19日06:03:56,开始学习垃圾回收,学习资料来源(张龙老师的JVM课程) JVM内存数据区域知识复习 学习垃圾回收之前,要对JVM内部的内存区域有详 ...

随机推荐

  1. UILabel设置行间距

    UILabel设置行间距: NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWi ...

  2. iOS:MBProgressHUD的基本使用

    下载地址:https://github.com/jdg/MBProgressHUD/ //方式1.直接在View上show HUD = [[MBProgressHUD showHUDAddedTo:s ...

  3. parallax corrected cubemap

    https://community.arm.com/graphics/b/blog/posts/reflections-based-on-local-cubemaps-in-unity https:/ ...

  4. NFSv4 mount incorrectly shows all files with ownership as nobody:nobody

    NFSv4 mount incorrectly shows all files with ownership as nobody:nobody   https://access.redhat.com/ ...

  5. How to rebuild RPM database on a Red Hat Enterprise Linux system?

    本文是笔者最近遇到的一个故障的处理过程,解决方案是Rebuild RPM 的DB,后面内容其实是REDHAT官方的solutions,不过我遇到的现象和解决方案都与官方有点出入,故一直帖出来: 我遇到 ...

  6. Selenium——去掉Chrome正受到自动软件测试的控制

    selenium打开chrome浏览器时,地址栏下方会出现该提示“Chrome正在受到自动测试软件的控制” public class BasicDriver { public WebDriver dr ...

  7. [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.

    1.滑动时候警告[Intervention] Unable to preventDefault inside passive event listener due to target being tr ...

  8. mui 事件绑定(on)

    除了可以使用addEventListener()方法监听某个特定元素上的事件外, 也可以使用.on()方法实现批量元素的事件绑定. 示例: 点击新闻列表,获取当前列表项的id,并将该id传给新闻详情页 ...

  9. cocos2d-x 重力感应 加速器的使用

    CSDN开通已有两三年,今天作为一名刚入行的菜鸟写下自己的第一篇Blog. 刚好项目中须要用到重力感应,google下发现重力感应的使用很easy. 例如以下: 第一步: 在当前层开启重力感应.函数: ...

  10. C语言中函数调用过程(如何管理栈空间)

    ps:先做草稿,以后有时间再整理并贴图,:) 主要是利用栈底寄存器(ebp).栈顶寄存器(esp)跟eax寄存器(存储返回值)来实现. 假设P调用Q: P() { Q(1,2); } (跟实际情况可能 ...