【JVM】垃圾回收策略

垃圾回收策略:

主要针对线程共享内存(堆,方法区)

1. 判断哪些对象需要回收:

在回收前先需要判断对象是否存活

  1. 引用计数法(Python,C++智能指针都用到了这些)
    给每个对象附加一个引用计数器,每当有一个引用指向当前对象,计数器 + 1,每当有引用不再指向当前对象,计数器值 - 1,任意时刻引用计数器值为 0 的对象就被标记为不再“存活”。
    缺点:无法解决循环引用问题(我中有你,你中有我)

  2. 可达性分析算法(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.已经确定死亡了的对象如何进行垃圾回收

方法区的回收(永久代回收):
方法区(永久代)的垃圾回收主要收集两部分内容 : 废弃常量和无用的类。

回收方法区
方法区(永久代)的垃圾回收主要收集两部分内容 : 废弃常量和无用的类。

  1. 回收废弃常量和回收Java堆中的对象十分类似。以常量池中字面量(直接量)的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个String对象引用常量池的"abc"常量,也没有在其他地方引用这个字面量,如果此时发生GC并且有必要的话,这个"abc"常量会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
  2. 判定一个类是否是"无用类"则相对复杂很多。类需要同时满足下面三个条件才会被算是"无用的类" :
  • 该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
  • 加载该类的ClassLoader已经被回收
  • 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法

  JVM可以对同时满足上述3个条件的无用类进行回收,也仅仅是"可以"而不是必然。在大量使用反射、动态代理等场景都需要JVM具备类卸载的功能来防止永久代的溢出。方法区的gc频率非常低。

堆的垃圾回收算法:

1.标记清除算法:
算法分为标记与清除两个阶段:

  • 标记阶段首先将需要回收的对象打上回收标记。
  • 清除阶段一次性回收所有被打上标记的对象空间。
    不足:
    1.效率低
    2.主要问题:标记清除会产生大量不连续空间碎片,导致gc频繁发生。

2.复制算法(新生代垃圾回收算法)
首先需要了解堆分为新生代与老年代。
对象的分配策略:
新生代
对象默认先在新生代产生,大部分对象在此区域存放,该区域对象特点是“对象朝生夕死”(存活率很低)
老年代

  1. 大对象直接进入老年代,虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的在于避免Eden区以及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存)
  2. 长期存活对象(默认新生代对象在From和To之间移动15次(默认值可修改))进入老年代
  3. 动态年龄判断:若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)与老年代空间,其中新生代采用复制算法,老年代采用标记整理算法。

  1. Minor GC又称为新生代GC : 指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。
  2. Full GC 又称为 老年代GC或者Major GC : 当新生代空间不够用时需要从老年代借助空间(老年代的分配担保) ,当借助后还是不够用的时候,就需要清理一些老年代空间出来。所以出现了Major GC,经常会伴随至少一次的Minor G
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我顶得了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值