您的位置:首页 > 其它

垃圾回收器

2015-06-14 08:51 309 查看

引用计数(Reference Count)

原理

在每个对象中保存该对象的引用计数,当引用发生增减时对引用计数进行更新。

引用计数的增减:一般发生在变量赋值、对象内容更新、函数结束(局部变量不再被引用)等时间点

引用计数变为0:代表该对象不被引用,可以进行回收。

优点

容易实现

对象在不被引用的瞬间释放,由于GC而产生的中断时间比较短

缺点

无法释放循环引用的对象

不适合并行处理

引用计数并不适合并行处理。为了在多线程环境下避免多个线程同时操作引用计数,引用计数的操作必须采用独占的方式来进行,如果引用计数操作频繁发生,每次都要使用加锁等并发控制机制的话,会造成较大的开销。

标记清除(Mark & Sweep)

原理

将存储器视为一张有向可达图,节点被分为一组根节点(根节点不在堆中,包含指向堆的指针)与一组堆节点。

存在一条从任意根节点出发并到达p的有向路径时,称p是可达的。不可达点对应于垃圾。

垃圾收集器即维护可达图的某种表示,并定期回收不可达点。

标记阶段(Mark)标记出根节点的所有可达和已分配的后继,清除阶段(Sweep)释放每个未被标记但已分配的块。

复制收集(Copy and Collection)

原理

将所有没有死亡的对象复制到新的空间中,然后将旧的空间废弃掉,就可以将死亡对象所占用的空间一口气全部释放出来,而没有必要再次扫描每个对象。下次GC的时候,现在的新空间也就变成了将来的旧空间。

算法中,会将从根开始被引用的对象复制到另外的空间中,然后,再将复制的对象所能够引用的对象用递归的方式不断复制下去。

分代回收

分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。具体来说,包括以下特点:

最近被分配内存空间的对象最有可能需要被释放。

在方法被执行前,通常需要为该方法所使用到的对象分配内存空间,搜索最近被分配的对象集合有助于花费最少的工作来释放进可能多的空闲内存空间。


生命期最长的对象需要释放的可能性最小。

在通过几轮垃圾回收后仍然存在的对象不大可能是那种能够在下一轮回收中被释放的临时对象,搜索这些内存块往往要进行大量的工作,却只能释放很小一部分的内存空间。


同时分配内存的对象通常也会同时使用。

将同时分配内存的对象存储位置彼此相连有助于提高缓存性能。


因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

Python中的GC

Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。引用计数最大缺陷就是循环引用的问题,所以Python采用了辅助方法,具体可以参考这篇博文

多线程Python 程序运行得比其只有一个线程的时候还要慢?

这是大多数Python程序员有过疑问的问题,网上也有了大量的回答,包括最经典的标准答案:“不要使用多线程,请使用多进程”。在所有的保留GIL的理由中,移除GIL会较大幅度地减低单线程程序的速度可能是最重要的了。

上面提到,Python采用的是以引用计数为主垃圾回收机制,在多线程环境下,需要使用加锁等并发控制机制保证引用计数的独占操作,而GIL对诸如当前线程状态和为垃圾回收而用的堆分配对象这样的东西的访问提供着保护,从而保证了单线程程序的高效。

Python有着很多不带GIL的解释器,这些解释器能够带来更好的多线程性能。此外,当然你真的需要充分利用多核的速度优势,此时python可能并非你最佳的选择,请考虑别的语言吧,如C++,Java,erlang 等。

C#与Java中的GC

值类型与引用类型区别

值类型存储在栈中,栈通常使用的一级缓存,存取速度较快

引用类型存储在堆中,栈中存放的是堆中数据存放的地址(root),堆通常使用的二级缓存,存取速度较慢

C#与Java中,采用的是分代的GC策略,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

.NET框架中的垃圾回收器将分配的对象划分为3代。分别为0、1、2代。参考博文

Java中分为年轻代、年老代、持久带。参考博文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息