GC垃圾回收机制
一、GC概述
垃圾收集 Garbage Collection 通常被称为“GC”
jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.
1、GC回收判定
引用计数法
可达性分析法
2、GC回收算法
标记清除算法
标记复制算法
标记整理算法
分代回收算法
复制算法
3、GC收集器
串行收集器:
Serial(young)、Serial Old(old)
并行收集器:
ParNew(young)、Parallel Scavenge(young)、Parallel Old(old)
并发收集器:
CMS(old)、G1收集器
二、判断对象是否存活
JVM空间不够就需要Garbage Collection
了,一般共享区的都要被回收比如堆区以及方法区。
在进行内存回收之前要做的事情就是判断那些对象是死的,哪些是活的。
常用方法有两种 引用计数法
跟 可达性分析
。
1、引用计数法
思路:
给 Java 对象添加一个引用计数器,每当有一个地方引用它时,计数器 +1;
引用失效则 -1,当计数器不为 0 时,判断该对象存活;否则判断为死亡(计数器 = 0)。
优点:
实现简单,判断高效。
缺点:
无法解决 对象间 相互循环引用 的问题
code:
class GcObject {
public Object instance = null;
}
public class GcDemo {
public static void main(String[] args) {
GcObject object1 = new GcObject(); // step 1
GcObject object2 = new GcObject(); // step 2
object1.instance = object2 ;//step 3
object2.instance = object1; //step 4
object1 = null; //step 5
object2 = null; // step 6
}
}
step1: GcObject实例1的引用计数+1,实例1引用数 = 1
step2: GcObject实例2的引用计数+1,实例2引用数 = 1
step3: GcObject实例2的引用计数+1,实例2引用数 = 2
step4: GcObject实例1的引用计数+1,实例1引用数 = 2
step5: GcObject实例1的引用计数-1,结果为 1
step6: GcObject实例2的引用计数-1,结果为 1
如上分析发现实例1跟实例2的引用数都不为0而又相互引用,这两个实例所占有的内存则无法释放。
2、可达性分析法
很多主流商用语言(如Java、C#)都采用引用链法
判断对象是否存活,
大致的思路就是将一系列的 GC Roots 对象作为起点,从这些起点开始向下搜索。
在Java语言中,可作为 GC Roots 的对象包含以下几种:
- 第一种是虚拟机栈中的引用的对象,在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。
- 第二种是我们在类中定义了全局的静态的对象,也就是使用了
static
关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。 - 第三种便是常量引用,就是使用了
static final
关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。 - 第四种是在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用Native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。
GC Root步骤主要包含如下三步:
1、可达性分析:
当一个对象到 GC Roots 没有任何引用链相连时,则判断该对象不可达。
注意: 可达性分析仅仅只是判断对象是否可达,但还不足以判断对象是否存活 / 死亡。
2、第一次标记 & 筛选
筛选的条件对象 如果没有重写finalize或者调用过finalize
则将该对象加入到F-Queue中
finalize()
类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。
3、第二次标记 & 筛选
当对象经过了第一次的标记 & 筛选,会被进行第二次标记 & 准备被进行筛选。
经过F-Queue筛选后如果对象还没有跟GC Root建立引用关系则被回收,属于给个二次机会。
Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容:
虚拟机栈中引用的对象
本地方法栈中引用的对象
方法区中类静态属性引用的对象
方法区中的常量引用的对象
3、四大引用类型
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
Java 提供了四种强度不同的引用类型。
1、强引用
强引用(StrongReference)是使用最普遍的引用。
垃圾回收器绝对不会回收它,内存不足时宁愿抛出OOM导致程序异常,平常的new 对象就是。
使用 new 一个新对象的方式来创建强引用。
Object obj = new Object()