GC 算法中最重要的两个角色就是 Mutator 和 Collector。
Mutator 的本意是改变者。因为我们所写的所有 Java 程序,都有可能改变对象的存活和死亡状态,以及它们之间的引用关系,那么这些 Java 程序就是 Mutator。
Collector 用于回收空间中的垃圾,所以叫做收集者。
所有不在堆中,而指向堆里的引用都是根引用,根引用所指向的对象就是“确定的活跃对象”,这些对象是根对象。根引用的集合就是根集合。
引用计数算法
为了记录一个对象有没有被其他对象引用,我们可以在每个对象的头上添加一个叫“计数器”的东西,用来记录有多少其他对象引用了它。
引用计数具备以下优点:
- 可以立即回收垃圾。因为每个对象在被引用次数为 0 的时候,是立即就可以知道的,所以一旦一个对象成为垃圾,它将立即被释放;
- 没有暂停时间。对象的回收根本不需要另外的 GC 线程专门去做,业务线程自己就搞定了,所以引用计数算法不需要停顿时间。
引用计数也存在以下缺点:
- 在每次赋值操作的时候都要做额外的计算。在多线程的情况下,为了正确地维护引用计数,需要同步和互斥操作,这往往需要通过锁来实现,这会对多线程程序性能带来比较大的损失;
- 会有链式回收的情况。比如多个对象对链表形式串在一起,它们的引用计数都为 1,当链表头被回收时,整个链表都会回收,这可能会导致一次回收所使用的时间过长;
- 循环引用。如果 objA 引用了 objB,objB 也引用了 objA,但是除此之外,再没有其他的地方引用这两个对象了,这两个对象的引用计数就都是 1。这种情况下,这两个对象是不能被回收的。如果说上面两条缺陷还可以克服的话,那么循环引用就是比较致命的。
在使用引用计数算法进行内存管理的语言中,比如 Python 和 Swift,都会存在循环引用的问题。Python 在引用计数之外,另外引入了三色标记算法,保证了在出现循环引用的情况下,垃圾对象也能被正常回收。
引用计数实现方便,又可以做到对无用的资源进行立即回收,但他无法应对高并发、高吞吐的场景。
此文章为7月Day19学习笔记,内容来源于极客时间《编程高手必学的内存知识》