GC需要完成的3件事情:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
今天这篇文章就是说明哪些内存需要回收。
在Java内存区域 一篇已经说明Java内存的各个数据区域的特点,其中程序计数器、虚拟机栈、本地方法3个区域随线程而生,随线程而灭,所以这几个区域的内存分配和回收都具备确定性,不用考虑回收问题。而Java堆和方法区则不一样,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。
1. 判断对象是否可以回收
垃圾收集器在对堆进行回收前,第一件事就是要确定这些对象之中哪些还“存活”着,哪些已经死去。
1.1. 引用计数算法
- 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1。
- 当引用失效时,计数器减1。
- 任何时刻计数器为0的对象就是不可能再被使用的。
优点:实现简单,判断效率高。
缺点:无法解决对象之间相互循环引用的问题。如以下例子,对象objA和objB存在相互引用的现象,实际上两个对象已经不可能被访问,但是它们因为互相引用着对方,导致它们的引用计数器都不为0,所以无法回收。下面给出图示。
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
1.2. 可达性分析算法
通过一系列”GC Roots”对象作为起始点,从这些节点开始向下搜索,搜索所走过和路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(从GC Roots到这个对象不可达),则证明该对象是不可用的。
如下图对象object5、object6、object7虽然互相有关联,但是它们到GC Root是不可达的,所以它们将会被判断为是可回收的对象。
Java中,可作为GC Roots的对象包括:
- 虚拟机栈(栈帧中本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象。
2. Java中的引用类型
JDK1.2之前的引用定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
JDK1.2后的引用定义:将引用分为强引用、软引用、弱引用、虚引用,引用强度依次减弱。
- 强引用:是指在程序代码之中普遍存在的,类似 “Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象。
- 软引用:是用来描述一些还有用但并非需要的对象,对于软引用关联着的对象,在系统将要发生内存异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存异常。
- 弱引用:也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存释放足够,都会回收掉只被弱引用关联的对象。
- 虚引用:也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,对一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
3. 对象的生存与死亡
即使在可达性分析算法中不可达的对象,要真正死亡,至少要经历两次标记过程。
- 第一次标记:对象没有雨GC Roots相连接的引用链,然后进行筛选看看是否必要执行
finalize()
方法
- 没有必要执行:
- 对象没有覆盖
finalize()
方法 finalize()
方法已经被虚拟机调用过
- 对象没有覆盖
- 有必要执行:对象将会放置在一个叫做
F-Queue
的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer
线程去执行它。
- 没有必要执行:
- 第二次标记:GC将对
F-Queue
中的对象进行第二次小规模的标记,此时finalize()
方法是对象逃脱死亡的最后一次机会,要想逃脱死亡,则需满足:重新与引用链上的任何一个对象建立关联即可。如果没有就将被垃圾收集器回收