在垃圾回收之前,首先要判断哪些对象还活着、哪些对象已经死去(不可能再通过任何途径使用)
一、引用计数法
引用计数就是给对象设置一个计数器,每有一个引用指向它,计数器加一;引用失效时计数器减一。当计数器为0时,表示对象不可达。
但是这样存在循环引用的问题:假设A和B互相引用,并且引用计数器的值都为1。这样A和B都不可达了,但由于计数器的值不为0,他们不会被回收。因此主流的Java虚拟机包括hotspot都没有使用引用计数法。
二、可达性分析法
hotspot用可达性分析法判断对象是否可达,算法的思想是:以 GC Roots 作为起点根据引用关系向下搜索,搜索走过的路径称为 引用链(reference chain)。如果某个对象到 GC Roots 没有任何引用链相连,这个对象就是不可达的。
在Java中,GC Roots 包括这几个:
- 栈帧本地变量表引用的对象
- 方法区类静态属性引用的变量
- 方法区中的常量引用对象
- JNI引用的对象
- 虚拟机内部的引用:基本数据类型对应的class对象、常驻的异常对象(NullPointExcepiton等)、系统类加载器
- synchronized持有的对象
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不
同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。
三、四种引用类型
- 强引用
指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
- 软引用
用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常
- 弱引用
比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象
- 虚引用
它是最弱的一种引用关系。一个对象是否有虚引用的
存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚
引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知
四、回收对象
对象被可达性算法判定为不可达后,并不会立即回收。回收一个对象,至少要经过两次标记:
- 发现一个对象不可达后,进行一次标记。随后判断对象是否有必要执行finalize方法,如果对象没有覆盖或者已经执行过一次finalize方法就不需要再执行。
如果需要执行finalize,把对象放在F-Queue队列中等待执行(不保证finalize能运行结束)。这期间有可能对象又被重新引用。 - 第二次标记查看对象是否被GC Roots引用,如果还没有的话就回收对象
finalize只是为了模仿c++析构函数设计出来的,运行代价高、不确定性大。写代码时最好用try-finally代替
五、回收方法区
方法区(hotspot中的元空间)也是有垃圾回收行为的,回收的内容分两部分:废弃的常量、不再使用的类型。
- 判断常量废弃只需要看常量与GC Roots是否有引用链就行了
- 类型需要满足下面三个条件才属于 不再被使用的类:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例
- 加载该类的类加载器已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
方法区垃圾回收在热部署中很常见。我们创建一个守护线程监听java源文件是否被修改,一旦修改后就创建新的类加载器去重新加载被修改的类,这样旧的类加载器和旧的类都不会再被使用,也就意味着可以回收了