概述

垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。

引用计数法:指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为 0 就会回收但是 JVM 没有用这种方式,因为无法判定相互循环引用(A 引用 B,B 引用 A) 的情况
可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

引用计数法


给对象中添加一个引用计数器(counter 计数器),每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;当计数器为 0 时,就认为该对象无效了。

优点:快,方便,实现简单。
缺陷:对象相互引用时(A.instance=B同时B.instance=A),很难判断对象是否该回收。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。发生循环引用的对象的引用计数永远不会为0,结果这些对象就永远不会被释放。

所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们,所以主流的虚拟机都没有采用这种算法。

  1. public class ReferenceCountingGc {
  2. Object instance = null;
  3. public static void main(String[] args) {
  4. ReferenceCountingGc objA = new ReferenceCountingGc();
  5. ReferenceCountingGc objB = new ReferenceCountingGc();
  6. objA.instance = objB;
  7. objB.instance = objA;
  8. objA = null;
  9. objB = null;
  10. }
  11. }

GC Roots可达性分析(Java中使用)

什么是GC Root

在JVM中, 有一个根对象的概念, 就是堆里面的东西和他们有关系的就是要用的, 没有关系的,就是可以回收的了.我们来这样分类记忆

可作为GC Roots的对象

虚拟机栈(栈桢中的本地变量表)中的引用的对象
方法区类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈 JNI 引用的对象

如何判断一个常量是废弃常量

运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?
假如在常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池。

如何判断一个类是无用的类

方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类”

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

为什么要有GC ROOT? 引用计数法缺陷

判断对象是否可以被回收之引用计数法

Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。
因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,给对象中添加一个引用计数器,每当有一个地方引用它,计数器值加1,每当有一个引用失效时,计数器值减1。

任何时刻计数器值为零的对象就是不可能再被使用的,那么这个对象就是可回收对象。

那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。
判断对象是否死亡:引用计数器和GCRoot - 图1



判断对象是否可以被回收之枚举根节点可达性分析
1. 为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。
2. 所谓”GCroots,或者说tracingGC的“根集合”就是一组必须活跃的引用。
3. 基本思路就是通过一系列名为”GCRoots”的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GCRoots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,没有被遍历到的就自然被判定为死亡。




判断对象是否死亡:引用计数器和GCRoot - 图2
判断对象是否死亡:引用计数器和GCRoot - 图3

判断对象是否死亡:引用计数器和GCRoot - 图4


Tracing GC的根本思路就是:给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,其余对象(也就是没有被遍历到的)就自然被判定为死亡。注意再注意:tracing GC的本质是通过找出所有活对象来把其余空间认定为“无用”,而不是找出所有死掉的对象并回收它们占用的空间。GC roots这组引用是tracing GC的起点。要实现语义正确的tracing GC,就必须要能完整枚举出所有的GC roots,否则就可能会漏扫描应该存活的对象,导致GC错误回收了这些被漏扫的活对象。



这些引用可能包括:
1.所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
2.VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。
3.JNI handles,包括global handles和local handles
4.(看情况)所有当前被加载的Java类
5.(看情况)Java类的引用类型静态变量
6.(看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)
7.(看情况)String常量池(StringTable)里的引用

注意,是一组必须活跃的引用,不是对象。

什么是可达性分析法

可达性分析法:通过一系列“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象不一定会成为可回收对象。进入DEAD状态的线程还可以恢复,GC不会回收它的内存。(把一些对象当做root对象,JVM认为root对象是不可回收的,并且root对象引用的对象也是不可回收的)

判断对象是否死亡:引用计数器和GCRoot - 图5

对象被判定可被回收,需要经历两个阶段

(1) 第一个阶段是可达性分析,分析该对象是否可达
(2) 第二个阶段是当对象没有重写finalize()方法或者finalize()方法已经被调用过,虚拟机认为该对象不可以被救活,因此回收该对象。(finalize()方法在垃圾回收中的作用是,给该对象一次救活的机会)

方法区中的垃圾回收

(1) 常量池中一些常量、符号引用没有被引用,则会被清理出常量池
(2) 无用的类:被判定为无用的类,会被清理出方法区。判定方法如下:A、 该类的所有实例被回收B、 加载该类的ClassLoader被回收C、 该类的Class对象没有被引用

finalize():

(1) GC垃圾回收要回收一个对象的时候,调用该对象的finalize()方法。然后在下一次垃圾回收的时候,才去回收这个对象的内存。
(2) 可以在该方法里面,指定一些对象在释放前必须执行的操作。

请忘记 finalize

finalize 是Object的方法
finalize可以完成对象的拯救(不被垃圾回收),但是JVM不保证这个方法一定能执行,所以请忘记这个方法,写代码的时候基本不要去用,只要了解就行了。