判断是否是垃圾
橙色的三个区域的回收机制是确定的,只有绿色区域的回收是动态的,因此垃圾回收只考虑绿色区域。
引用计数法
给每个对象添加一个引用计数器,每当有一个地方引用它,计数器就+1,当引用失效,计数器就-1,计数器为0的对象就可以被当作垃圾收集了。
缺点:无法检测出循环引用的情况
public class ReferenceCounterProblem {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.childNode = object2;
object2.childNode = object1;
}
}
可达性分析算法
把所有引用的对象想象成一棵树,从树的根节点(GC Boots)出发,遍历找到所有树枝对象,这些称为“可达”对象,不能到达的则为可回收对象。
以下对象可以作为GC Boots:
- 虚拟机栈(帧栈中的本地变量表)中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI 引用的对象
垃圾回收算法
标记-清理
利用可达性遍历内存,将“存活”对象和“垃圾”对象进行标记,然后再遍历一遍,把所有垃圾对象所占的空间清除即可。
- 简单方便
- 容易产生内存碎片
标记-整理
利用可达性遍历内存,将“存活”对象和“垃圾”对象进行标记,再把所有存活对象堆到同一个地方。
- 适合存活对象多、垃圾少的情况
- 需要整理过程
复制
将内存按容量划分成大小相等的两块,每次只用其中一块,当一块用完后,将还活着的对象复制一份到另一块上,再将当前块一次性清理掉。
- 简单
- 不会产生碎片
- 空间利用率低,只用到了一半
堆和方法区的垃圾回收
方法区又叫做永久代。永久代的垃圾回收主要有两部分:废弃常量和无用的类。
废弃常量垃圾回收
-
判定一个常量是否是废弃常量:没有任何一个地方对这个常量进行引用就表示是废弃常量。
-
垃圾回收
无用的类垃圾回收
第一步:判定一个类是否是“无用的类”:需要满足下面三个条件
- Java堆中不存在该类的任何实例,也就是该类的所有实例都被回收
- 加载该类的ClassLoader已经被回收
- 该类对应的Class对象在任何地方没有引用了,也不能通过反射访问该类的方法。
第二步:满足上面三个条件就可以回收了,但不是强制的。
注意:《java虚拟机规范》里面曾经说到过,不要求虚拟机对方法区进行垃圾回收。而且方法区进行垃圾回收性价比比较低。
堆的垃圾回收
- 新生代:刚刚创建的对象,存活对象少,垃圾多
- 老年代:存活了一段时间的对象,存活对象多,垃圾少
新生代:复制-回收机制
对于新生代区域,由于每次 GC 都会有大量新对象死去,只有少量存活。因此采用 复制-回收算法,GC 时把少量的存活对象复制过去即可。但是从上面我们可以看到,新生代也划分了三个部分比例:Eden:S1:S2=8:1:1。
其中 Eden 意为伊甸园,形容有很多新生对象在里面创建;S1和S2中的S表示Survivor,为幸存者,即经历 GC 后仍然存活下来的对象。
工作原理如下:
首先,Eden对外提供堆内存。当 Eden区快要满了,触发垃圾回收机制,把存活对象放入 Survivor A 区,清空 Eden 区;Eden区被清空后,继续对外提供堆内存;当 Eden 区再次被填满,对 Eden区和 Survivor A 区同时进行垃圾回收,把存活对象放入 Survivor B区,同时清空 Eden 区和Survivor A 区;当某个 Survivor区被填满,把多余对象放到Old 区;当 Old 区也被填满时,进行 下一阶段的垃圾回收。
老年代:标记整理-回收机制
老年代的特点是:存活对象多、垃圾少。因此,根据老年代的特点,这里仅仅通过少量地移动对象就能清理垃圾,而且不存在内存碎片化。也就是标记整理的回收机制。
垃圾回收相关函数
finalize()
finalize()
是Object类中的方法,在垃圾回收器准备