现在的编程语言中大多都包括了垃圾回收(Garbage Collection)机制,垃圾回收机制是一种自动的内存管理机制,当计算机内存中的一个对象不再需要被使用时,就会自动的让出这块内存。在早期的C/C++编程语言中,程序员需要自己手动申请和释放内存,而因在编程的过程中往往会经常忘记释放那些不再使用的内存,进而造成内存泄漏。垃圾回收机制可以大大减轻程序员的负担,减少程序员犯错的机会。垃圾回收机制最早起源于LISP语言,目前的大多数高级语言都支持内存回收机制,比如:PHP、Java、C#、Erlang等等。
垃圾回收算法的原理:
- 推算出某个对象在未来的程序运行中将不再会被访问。
- 将这些对象占用的内存回收。
收集器实现
- 引用计数收集器 最早期的垃圾回收实现方法,通过对数据存储的物理空间附加多一个计数器空间,当有其他数据与其相关时则加一,反之相关解除时减一,定期检查各储存对象的计数器,为零的话则认为已经被抛弃而将其所占物理空间回收。是最简单的实现,但存在无法回收循环引用的存储对象的缺陷。
- 跟踪收集器 近现代的垃圾回收实现方法,通过定期对若干根储存对象开始遍历,对整个程序所拥有的储存空间查找与之相关的存储对象和没相关的存储对象进行标记,然后将没相关的存储对象所占物理空间回收。
回收算法
主要的回收算法可以分为以下几类:
- 标记-清除 先暂停整个程序的全部运行线程,让回收线程以单线程进行扫描标记,并进行直接清除回收,然后回收完成,恢复运行线程。会导致大量零碎的空闲空间碎片,导致大容量对象不容易获得连续的内存空间,而造成空间浪费。
- 标记-压缩 和“标记-清除”相似,不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间。从而集成空闲空间。
- 复制 需要程序将所拥有的内存空间分成两个部分。程序运行所需的存储对象先存储在其中一个分区(定义为“分区0”)。同样暂停整个程序的全部运行线程后,进行标记后,回收期间将保留的存储对象搬运汇集到另一个分区(定义为“分区1”),完成回收,程序在本次回收后将接下来产生的存储对象会存储到“分区1”。在下一次回收时,两个分区的角色对调。
- 增量回收器 需要程序将所拥有的内存空间分成若干分区。程序运行所需的存储对象会分布在这些分区中,每次只对其中一个分区进行回收操作,从而避免程序全部运行线程暂停来进行回收,允许部分线程在不影响回收行为而保持运行,并且降低回收时间,增加程序响应速度。
- 分代 由于“复制”算法对于存活时间长,大容量的储存对象需要耗费更多的移动时间,和存在储存对象的存活时间的差异。需要程序将所拥有的内存空间分成若干分区,并标记为年轻代空间和年老代空间。程序运行所需的存储对象会先存放在年轻代分区,年轻代分区会较为频密进行较为激进垃圾回收行为,每次回收完成幸存的存储对象内的寿命计数器加一。当年轻代分区存储对象的寿命计数器达到一定阈值或存储对象的占用空间超过一定阈值时,则被移动到年老代空间,年老代空间会较少运行垃圾回收行为。一般情况下,还有永久代的空间,用于涉及程序整个运行生命周期的对象存储,例如运行代码、数据常量等,该空间通常不进行垃圾回收的操作。 通过分代,存活在局限域,小容量,寿命短的存储对象会被快速回收;存活在全局域,大容量,寿命长的存储对象就较少被回收行为处理干扰。
实现
上面是垃圾回收的基本算法,有些编程语言的垃圾回收机制会使用上面的算法然后再自己进行改造优化性能。比如Erlang就同时使用分代回收和标记清除的算法来实现垃圾回收机制。