垃圾回收策略:
主要针对线程共享内存(堆,方法区)
1. 判断哪些对象需要回收:
在回收前先需要判断对象是否存活
-
引用计数法(Python,C++智能指针都用到了这些)
给每个对象附加一个引用计数器,每当有一个引用指向当前对象,计数器 + 1,每当有引用不再指向当前对象,计数器值 - 1,任意时刻引用计数器值为 0 的对象就被标记为不再“存活”。
缺点:无法解决循环引用问题(我中有你,你中有我) -
可达性分析算法(Java、C#、Lisp)
通过一系列被称为GC Roots的对象开始向下搜寻,若到指定对象有路可走(“可达”),认为此对象存活,若从任意一个GC Roots对象到目标对象均不可达,认为目标对象已经不在存活。
哪些对象可以作为GC Roots:
- 虚拟机栈和本地方法中的临时变量指向的对象
- 类中静态变量引用的对象
- 类中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象
例:
public class Test {
public Object instance = null;
private static int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC() {
//1
Test test1 = new Test();
//1
Test test2 = new Test();
//2
test1.instance = test2;
//2
test2.instance = test1;
test1 = null;
test2 = null;
// 强制jvm进行垃圾回收
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
从结果可以看出,GC日志包含" 6092K->856K(125952K)",意味着虚拟机并没有因为这两个对象互相引用就不回收他们。即JVM并不使用引用计数法来判断对象是否存活。
test1,test2属于虚拟机栈中的临时变量指向的对象可以作为GC Roots
但是instance作为一个类中普通属性不能作为GC Roots所以即使他指向了也没有意义。(同图中的object 5,6,7)
2. 对象自我拯救(finalize)
JVM在进行GC之前,需要判断即将回收的对象所在的类是否覆写了finalize()?
a. 若没有被覆写,此对象直接被回收
b. 若对象所在的类覆写了finalize
- 若finalize() 未被JVM调用果,则会调用finalize(),若对象在此次调用过程中与GC Roots有路可走,此对象将不在被回收
- 若finalize()被JVM调用过,那么此对象直接被回收。
/**
* @program: jvmTest
* @description: 自我拯救
* @author: fwb
* @create: 2019-07-27 17:14
**/
public class FinalizeTest {
public static FinalizeTest finalizeTest;
public void isAlive() {
System.out.println("I am alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
finalizeTest = this;
}
public static void main(String[] args)throws Exception {
finalizeTest = new FinalizeTest();
finalizeTest = null;
System.gc();
Thread.sleep(500);
if (finalizeTest != null) {
finalizeTest.isAlive();
}else {
System.out.println("now,I am dead :(");
}
// 下面代码与上面完全一致,但是此次自救失败
finalizeTest = null;
System.gc();
Thread.sleep(500);
if (finalizeTest != null) {
finalizeTest.isAlive();
}else {
System.out.println("now,I am dead :(");
}
}
}
结果:
从上面代码示例我们发现,finalize方法确实被JVM触发,并且对象在被收集前成功逃脱。但是从结果上我们发现,两个完全一样的代码片段,结果是一次逃脱成功,一次失败。这是因为,任何一个对象的finalize()方法都只会被系统自动调用一次,如果相同的对象在逃脱一次后又面临一次回收,它的finalize()方法不会被再次执行,因此第二段代码的自救行动失败。
final、finally、finalize的区别
final:终接器
1.被final修饰的类不能有子类
2.被final修饰的值不能更改
3.被final修饰的方法不能被覆写
finally:用在异常体系中
作用:保证重点代码一定会被执行
finalize:
Object类提供的一个方法,对象的自我拯救。
3.已经确定死亡了的对象如何进行垃圾回收
方法区的回收(永久代回收):
方法区(永久代)的垃圾回收主要收集两部分内容 : 废弃常量和无用的类。
技
回收方法区
方法区(永久代)的垃圾回收主要收集两部分内容 : 废弃常量和无用的类。
- 回收废弃常量和回收Java堆中的对象十分类似。以常量池中字面量(直接量)的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个String对象引用常量池的"abc"常量,也没有在其他地方引用这个字面量,如果此时发生GC并且有必要的话,这个"abc"常量会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
- 判定一个类是否是"无用类"则相对复杂很多。类需要同时满足下面三个条件才会被算是"无用的类" :
- 该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
- 加载该类的ClassLoader已经被回收
- 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法
JVM可以对同时满足上述3个条件的无用类进行回收,也仅仅是"可以"而不是必然。在大量使用反射、动态代理等场景都需要JVM具备类卸载的功能来防止永久代的溢出。方法区的gc频率非常低。
堆的垃圾回收算法:
1.标记清除算法:
算法分为标记与清除两个阶段:
- 标记阶段首先将需要回收的对象打上回收标记。
- 清除阶段一次性回收所有被打上标记的对象空间。
不足:
1.效率低
2.主要问题:标记清除会产生大量不连续空间碎片,导致gc频繁发生。
2.复制算法(新生代垃圾回收算法)
首先需要了解堆分为新生代与老年代。
对象的分配策略:
新生代:
对象默认先在新生代产生,大部分对象在此区域存放,该区域对象特点是“对象朝生夕死”(存活率很低)
老年代:
- 大对象直接进入老年代,虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的在于避免Eden区以及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存)
- 长期存活对象(默认新生代对象在From和To之间移动15次(默认值可修改))进入老年代
- 动态年龄判断:若From和To区相同年龄对象的和超过Survivor空间的一半,将所有此年龄的对象直接晋升老年代。
由于新生代中98%的对象都是"朝生夕死"的,所以不需要按照1:1的比例来分配空间,而是将新生代内存分为一块较大的Eden(伊甸园)与两块大小相等的Survivor(幸存者区),默认比例为8:1:1,每次使用Eden与其中一块Survivor区域(一个叫From区,一个叫To区)
Step1.
对象默认都在Eden区产生,当Eden区即将满时,触发一次Minor GC(新生代GC),将Eden区所有存活对象(一般只有2%)复制到From区,然后一次性清理掉Eden区的所有空间。
Step2.
(此时From已经有存活对象了)当Eden区域再次即将满的时候,触发Minor GC,此时需要将Eden与From区的所有存活对象复制到To区,然后一次性清理掉Eden与From的所有空间。
Step3.
当Eden区域再次即将满的时候,触发Minor GC,此时需要将Eden与To区的所有存活对象复制到From区,然后一次性清理掉Eden与To的所有空间。
之后的新生代Gc重复Step2,3。
注:
某些对象来回在From与To区交换若干次以上(默认15次)以上,将其置入老年代空间。
3. 标记整理算法(老年代的垃圾回收算法)
相较于标记清除:整理阶段先让存活对象向一端移动,而后清理掉存活对象边界之外的所有空间。避免产生不连续的空间碎片。
由于老年代存活率很高所以老年代不使用复制算法。
4. 分代收集策略(JavaGC)
将堆空间分为新生代(-Xmn)与老年代空间,其中新生代采用复制算法,老年代采用标记整理算法。
- Minor GC又称为新生代GC : 指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。
- Full GC 又称为 老年代GC或者Major GC : 当新生代空间不够用时需要从老年代借助空间(老年代的分配担保) ,当借助后还是不够用的时候,就需要清理一些老年代空间出来。所以出现了Major GC,经常会伴随至少一次的Minor G