一 垃圾回收概述

1.1 什么是垃圾

1)垃圾收集,不是Java语言的半生产物。早在1960年,第一门开始使用内存动态分配和垃圾收集技术的Lisp语言诞生。
2)关于垃圾回收三个经典的问题

  1. 哪些内存需要回收
  2. 什么时候回收
  3. 如何回收

3)垃圾收集机制是Java语言的招牌能力,极大的提高了开发效率。如今,垃圾收集几乎成为现代语言的标配,即使经过了如此长时间的发展,Java的垃圾收集机制仍然在不断的演进中,不同大小的设备,不同特征的应用场景,堆垃圾收集提出了新的挑战,这也是面试的重点。
4)垃圾是指运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
5)如果不及时对内存中的垃圾进行回收清理。那么这些垃圾对象所占的内存空间就会一直保留到应用程序结束,被保留的空间无法被其他对象所使用。甚至可能会导致内存溢出。

1.2 为什么需要垃圾回收

1)对于高级语言来说,一个基本认知就是如果不进行垃圾回收的话,内存迟早会消耗完,因为这样不断地分配内存空间而不进行垃圾回收,就好像不停的生产生活垃圾而从来不打扫清理一样。
2)出来释放没用的对象,垃圾回收也可以清除内存中的记录碎片。碎片整理将所占用的堆内存移到堆的一段,以便JVM将整理出来的内存分配给新的对象。
3)随着应用程序所应付的业务越来越大,复杂,用户越来越多,没有GC就不能保证应用程序的正常进行。而经常造成STW的GC又跟不上实际的需求,所以才会不断的尝试对GC进行优化。

1.4 早期的垃圾收集

1)在早期的C/C++时代,垃圾回收基本是手工进行的。开发人员可以使用new关键字进行内存申请,并使用delete关键字进行内存释放。
2)这种方式可以灵活控制内存的释放时间,但是会给开发人员带来频繁申请和释放内存的管理负担。倘若有一处内存区间由于程序员编码的问题忘记被回收,那么就会产生内存泄漏,垃圾对象永远无法清除。
3)现在除了Java外,C#,Python,Ruby等语言都使用了自动垃圾回收的思想,也是未来的趋势。可以说,这种自动化的内存分配和垃圾回收的方式已经成为现代开发语言必备的标准。

1.5 Java的垃圾回收机制

1)自动内存管理,无需开发人员手动参与内存的分配和回收,这样降低了内存泄漏和内存溢出的风险。如果没有垃圾回收器,Java也会和CPP一样,各种悬垂指针,野指针,泄漏问题让开发者头疼。
2)自动内存管理机制,让程序员从繁重的内存管理中释放出来,可以更加专心注重业务代码的编写。
3)但是对于Java开发者来说,有利必有弊。Java内存管理机制不用暴露在开发者之下,那么久会让开发者过度依赖于自动管理内存,忽略了底层的内存管理思想。这样严重弱化了Java开发者在程序出现内存溢出的时候对问题的定位和解决问题的能力。
4)此时,了解JVM的自动内存管理分配和内存回收原理就显得非常重要了,只有在真正了解了JVM是如何进行管理内存后,才能在遇见OOM时,快速地根据错误异常日志定位问题和解决问题。
5)当需要排查各种内存溢出,内存泄漏地问题时,当垃圾收集成为系统达到更高并发量地瓶颈时,就必须对这些自动化地技术实施必要地监控和调节。
6)垃圾回收器可以对年轻代回收,也可以对老年代回收,甚至是全堆,方法区地回收。其中,Java堆区是垃圾回收器地工作重点。
7)从次数上论:频繁收集Young区,较少收集Old区,基本不动Perm区。

二 垃圾回收算法

2.1 标记阶段:引用计数法

2.1.1 对象存活判断

1)在堆中存放着几乎所有的Java对象实例,在GCRoots执行垃圾回收前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个判断对象是否存活的过程叫作垃圾标记阶段。
2)简答来说:当一个对象已经不再被任何存活对象继续引用时,就可以判断其已经死亡。

3)判断一个对象存活的方式:引用计数法可达性分析法

2.1.2 引用计数法

1)引用计数法(Reference Counting)比较简单,对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。
2)对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,那么表示对象A不可能再被使用,就进行垃圾回收。
3)优点:实现简单,垃圾对象便于标识,判定效率高,回收没有延迟性。
3)缺点

  1. 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
  2. 每次赋值都需要更新计数器,伴随着加法和减法的操作 ,这样增加了时间开销。
  3. 引用计数器有一个严重的问题,那就是无法处理循环引用的情况。这是一条致命的缺陷,导致在Java的垃圾回收器中没有使用这类算法。

    2.1.3 总结

    1)引用计数法,是很多语言的资源回收选择,例如因人工智能而火热的Python,它同时支持引用计数法和垃圾收集机制。
    2)具体哪种最优需要看场景而定,业界有大规模实践中仅仅保留引用计数机制,以提高吞吐量的尝试。
    3)Java并没有选择引用计数这种机制,是因为很难解决循环引用的关系。
    4)Python中解决循环引用的方式:
  • 手动解除:就是在合适的时机,解除引用关系。
  • 使用弱引用weakref,这个是python的标准库,旨在解决循环引用。

    2.2 标记阶段:可达性分析算法

    2.2.1 概述

    1)相对于引用计数法而言,可达性分析算法不仅仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效的解决引用计数法中循环引用的问题,防止内存泄漏的发生。
    2)相较于引用计数法,这里的可达性分析算法就是Java、C#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集。
    对象地finalization机制
    MAT与JProfiler地GCRoots溯源
    清除阶段:标记-清除算法
    清除阶段:复制算法
    清除阶段:标记-压缩算法
    小结
    分代收集算法
    增量收集算法、分区算法