垃圾收集机制是Java的招牌能力,极大地提高了开发效率。如今垃圾收集几乎称为现代语言的标配,即使经过如此长时间的发展,Java的垃圾收集机制仍然在不断地演进中,不同大小的设备,不同特征的应用场景对垃圾收集提出了新的挑战,这当然也是面试人热点。
垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
通过前面的篇章我们知道,当栈区的方法执行完出栈之后,对应的引用也会出栈,但是该引用指向的对象还在堆中,这样堆中就会出现垃圾,需要回收。
如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。

Java垃圾回收主要关注的是方法区和堆区,Java栈,本地方法栈,程序计数器是没有GC行为的。
垃圾回收器可以对年轻代回收,也可以对老年代回收,甚至是全堆和方法区回收,其中,Java堆是垃圾收集器的工作重点。
从频率上讲:年轻代最频繁,养老区较少,永久代/元空间基本不回收。
在堆里放着几乎所有Java对象实例,在GC执行垃圾回收之前,首先需要区分内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉所占用的北村空间,因此这个过程我们称为垃圾标记阶段。
那么在JVM中酒精是如何标记一个死亡对象呢?简单说,当一个对象已经不再被任何存活对象继续引用时,就可以宣判已经死亡。
判断对象存活一般有两种方式:引用计数法和可达性分析算法。
引用计数算法比较简单,对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。
对于一个对象A,只要有任何一个对象引用了A ,则A的引用计数器就加1,当引用失效时,引用计数器就减1.只要对象A的引用计数器的值为0,即表示对象A不可能在被使用,可进行回收。
优点:
实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性。
缺点:

上图中p一个对象,后边的三个对象相互引用,形成了循环依赖。当我们将p的引用置空,由于三个对象的循环引用,导致了三者的计数器都不为0无法被回收,造成了内存泄漏。
可达性分析算法又叫做根搜索算法或者追踪性垃圾收集。
相较于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效的解决在引用计数算法中循环引用的问题,防止内存泄漏发生。这里的可达性分析就是Java,C#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集。

在Java语言中,GC Roots包括以下几类元素:

(红色是垃圾,蓝色不是垃圾)
除了这些固定的GC Roots集合外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象临时性的假如,共同构成完整GC Roots集合。比如:分带收集和局部收集。
如果只针对Java堆中的某一块区域进行垃圾回收,比如典型的只针对新生代,必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的,这个区域的对象完全有可能被其他区域的对象所引用,这时候就需要一并将关联的区域也加入GC Roots集合中去考虑,才能保证可达性分析的准确性。
小技巧:
由于ROOT采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个Root.
注意
如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保证一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。 这点也是导致GC进行时必须Stop The World的一个重要原因,即使是号称几乎不会发生停顿的CMS收集器,枚举根节点时也是必须要停顿的。