-本文主要介绍判断垃圾回收时机的俩种方式:引用计数算法和可达性分析算法,顺带说下强软弱虚引用和方法区的回收。
一、简介
- 本文主要介绍判断垃圾回收时机的俩种方式:引用计数算法和可达性分析算法,顺带说下强软弱虚引用和方法区的回收;
二、引用计数算法
2.1 原理
- 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为零的对象就是不可能再被使用的。
- 单纯的引用计数就很难解决对象之间循环引用的问题;
三、可达性分析算法
3.1 原理
- 基本思路就是通过一系列称为“GC Root”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Root间没有任何引用链相连,或者用图论的话来说就是从GC Root到这个对象不可达时,则证明此对象时不可能再被使用的。
3.2 俩次标记
- 即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候他们在暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历俩次标记过程;
- 如果对象在进行可达性分析后发现没有与GC Roots相链接的引用链,那它将会被第一次标记;
- 第一次被标记后会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法:假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过(一个对象的finalize()方法最多只会被系统自动调用一次),那么虚拟机将这俩种情况视为“没有必要执行”;
- 如果该对象被判定为有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法;这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束,原因是如果某个对象的finalize()方法执行缓慢,或者更极端的发生了死循环,将很可能导致F-Queue队列中的其他对象永久处于等待,甚至导致整个内存回收子系统崩溃;
- finalize()方法是对象逃脱死亡命运的最后一次机会,稍后收集器将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己:只要重新与引用链上的任何一对想建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了;
![]()
3.3 可以作为GC Root节点的对象
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,譬如java类的引用类型静态变量;
- 在方法区中常量引用的对象,譬如字符串常量池里的引用;
- 在本地方法栈JNI(通常所说的Native方法)引用的对象;
- java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExceptio、OutOfMemoryError)等,还有系统类加载器;
- 所有被同步锁(synchronized关键字)持有的对象;
- 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等;
- 除了上面那些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完成GC Root集合。
四、强软弱虚引用
4.1 强引用
- 强引用时最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obe = new Object()”这种引用关系;
- 无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象;
4.2 软引用
- 软引用是用来描述一些还有用,但非必须的对象;
- 只被软引用关联着的对象,在系统将要发生内存溢出前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常;
- 在jdk1.2版之后提供了SoftReference类来实现软引用;
4.3 弱引用
- 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止;
- 当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象;
- 在jdk1.2版之后提供了WeakReference类来实现弱引用;
4.4 虚引用
- 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系;
- 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例;
- 为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时受到一个系统通知;
- 在jdk1.2版之后提供了PhantomReference类来实现虚引用;
五、回收方法区
- 方法区的垃圾收集效率较低,主要回收俩部分内容:废弃的常量和不再使用的类型;
5.1 常量回收
- 举个常量池中字面量回收的例子,假如一个字符串“java”曾经进入常量池中,但是当前系统又没有任何一个字符串对象的值是“java”,换句话说,已经没有任何字符串常量池中的“java”常量,且虚拟机中也没有其他地方引用这个字面量。如果在这时发生内存回收,而且垃圾收集器判断确有必要的话,这个“java”常量就将会被系统清理出常量池。常量池中其他类(接口)、方法、字段的符号引用也与此类似;
5.2 类型回收
5.2.1 判定一个类型是否属于“不再被使用的类”的条件[不一定回收,需要结合虚拟机参数设置]:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例;
- 加载该类的类加载器已经被回收,这个i套件除非是经过精心设计的可替换类加载器的场景,如OSGi,JSP的重加载等,否则通常是很难达成的;
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法;