【JVM】【第十五章】垃圾回收相关算法


P138-目录
在这里插入图片描述
在这里插入图片描述


P139

1. 垃圾回收的3个阶段

  1. 垃圾标记阶段---->标记哪些为垃圾
  2. 对象终止阶段---->确定垃圾是否清除
  3. 垃圾清除阶段---->开始清除垃圾

2. 标记阶段 - 对象存活判断

垃圾回收主要针对的是JVM内存结构中,线程可共享的部分:堆和方法区;
频繁回收新生代,较少回收老年代,基本不动方法区;因此GC主要是针对堆内存进行讲解;
在这里插入图片描述

2.1 引用计数法 (java没有采用)

在这里插入图片描述

循环引用举例

循环引用导致的内存泄漏情况举例:链表节点之间的相互引用。
当p的指针断开的时候,内部的引用形成一个循环,这就是循环引用,从而造成内存泄漏
在这里插入图片描述
内存泄漏就是垃圾无法被回收;

注意: 这个内存泄漏的例子是针对引用计数算法的,java中并没有采用这个算法,因此实际中,不会出现这种情况;举此例子要说明其场景是在引用计数算法下的相互引用的情况。

证明:java使用的不是引用计数算法

package GC;

/**
 * -XX:+PrintGCDetails
 * 证明:java使用的不是引用计数算法
 *
 * @author shkstart
 * @create 2020 下午 2:38
 */
public class RefCountGC {
    //这个成员属性唯一的作用就是占用一点内存
    private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB

    Object reference = null;

    public static void main(String[] args) {
        RefCountGC obj1 = new RefCountGC();
        RefCountGC obj2 = new RefCountGC();

        obj1.reference = obj2;
        obj2.reference = obj1;

        obj1 = null;
        obj2 = null;

        //显式的执行垃圾回收行为,这里发生GC,obj1和obj2能否被回收?
        // System.gc();
    }
}

在这里插入图片描述

  • JVM堆内存中,存在两个RefCountGC对象,如果按照计数器法,那么这两个对象的计数为2,因此即使把obj1 .reference和obj2.reference(栈引用)置null。 则在Java堆当中的两块内存依然保持着互相引用,无法回收。
  • 但实际情况是,把obj1.reference和obj2.reference置为null之后,如果显示的去调用System.gc(),这里将会发生GC,JVM回收了堆中oj1和obj2实体。从而证明Java没有采用引用计数算法.

引用计数算法小结

  • 引用计数算法,是很多语言的资源回收选择,例如因人工智能而更加火热的Python,它更是同时支持引用计数和垃圾收集机制。

  • 具体哪种最优是要看场景的,业界有大规模实践中仅保留引用计数机制,以提高吞吐量的尝试。

  • Java并没有选择引用计数,是因为其存在一个基本的难题,也就是很难处理循环引用关系。Python如何解决循环引用?
    手动解除: 很好理解,就是在合适的时机,解除引用关系。
    使用弱引用weakref,weakref是Python提供的标准库,旨在解决循环引用。

2.2 可达性分析算法

P141

  • 别称: 根搜索算法、追踪性垃圾收集

在这里插入图片描述


可达性分析算法思路

可达性分析算法是以 根对象集合(GCRoots) 为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)

如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。
在这里插入图片描述

哪些是GC Roots?

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

可达性分析算法的注意事项

在这里插入图片描述
一致性就是对象的引用关系在GC Roots标记前后不发生改变,一旦改变了就不准确了;
stop the world 的时候会导致用户线程停顿;


3. 对象终止阶段-对象的 finalization 机制

P142

3.1 finalize() 方法机制

  • 对象销毁前的回调函数:finalize()
    当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法。

  • finalize() 允许开发人员提供对象被销毁之前的自定义处理逻辑。

  • finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等。

Object 类中 finalize() 源码

// 等待被重写
protected void finalize() throws Throwable {}

在这里插入图片描述

  • finanlize()方法不是手动调用的,而是由垃圾回收器调用的

3.2 finalize() 方法使用的注意事项

1. 永远不要主动调用某个对象的finalize()方法应该交给垃圾回收机制调用。

  • 如果你不重写finalize(),那么这个方法压根就不会执行,因为本身就没有具体实现。
  • 如果你重写了finalize(),也必须在可达性分析后此对象成被标记成了垃圾,并且得等到发生GC的时候,finalize()会进入一个优先级低的Finalizer线程中等待自动执行,不需要人为主动的去调用。

2.finalze()方法的特点
(1)执行finalize() 可能会导致对象复活。
(2)finalize()方法的执行时间是没有保障的,它完全由Gc线程决定。极端情况下,若不发生GC,则finalize()方法将没有执行机会。
(3)一个糟糕的finalize()会严重影响GC的性能。
(4)如果重写了finalize()方法,那么在垃圾对象被GC之前,总会先调用这个对象的finalize()方法。

从功能上来说,finalize()方法与C++ 中的析构函数比较相似,但是Java采用的是基于垃圾回收器的自动内存管理机制,所以finalize()方法在本质,上不同于C++ 中的析构函数。

3.3 对象是否"死亡"

一个经过可达性分析后,判定引用链断开的对象讲道理是需要被GC回收内存的,但是由于finalize()方法存在,此方法在对象被GC回收之前被调用,这就会导致此对象可能没有被回收。因此,由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态:
(1)可触及的:从GC Roots 引用链可以达到该对象。
(2)可复活的:GC Roots不可及,已经被标记为垃圾了,但是对象有可能在finalize()中复活。
(3)不可触及的:对象的finalize()被调用了,并且没有复活,那么就会进入不可触及状态。


finalize()只能被调用一次

  • 一个对象在被回收前,即使被finalize()救活了,那么他就不能再次调用finalize()了;

  • 不可触及的对象不可能被复活,因为finalize()只会被调用1次,在回收前就已经调用过了

以上3种状态中,只有在对象不可触及时才可以被回收。

3.4 判断对象是否可回收的两次标记过程

在这里插入图片描述
第一次标记:发现对象没有到GC Roots的引用链时
第二次标记:调用对象的finalize方法时


通过 JVisual VM 查看 Finalizer 线程:
在这里插入图片描述

3.5 代码测试对象可复活

P143

package GC;

/**
 * 测试Object类中finalize()方法,即对象的finalization机制。
 *
 * @author shkstart
 * @create 2020 下午 2:57
 */
public class CanReliveObj {
    public static CanReliveObj obj;//类变量,属于 GC Root


    //此方法只能被调用一次
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("调用当前类重写的finalize()方法");
        obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系,复活了
    }


    public static void main(String[] args) {
        try {
            obj = new CanReliveObj();
            // 对象第一次成功拯救自己
            obj = null;
            System.gc();//调用垃圾回收器
            System.out.println("第1次 gc");
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
            System.out.println("第2次 gc");
            // 下面这段代码与上面的完全相同,但是这次自救却失败了
            obj = null;
            System.gc();
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
执行结果:
	第1次 gc
	调用当前类重写的finalize()方法
	obj is still alive
	第2次 gc
	obj is dead

4.使用(MAT与JProfiler)工具分析GCRoots

P144 - P146


5.垃圾清除阶段

P147

  • 当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。

  • 目前在JVM中比较常见的三种垃圾收集算法是标记一清除算法( Mark一Sweep)复制算法(Copying)标记一压缩算法(Mark一Compact)

5.1 标记-清除算法

在这里插入图片描述
注意:标记的是可达对象!非垃圾!

在这里插入图片描述
在这里插入图片描述

5.2 复制算法

P148
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

5.3 标记-压缩(整理,Mark-Compact)算法

**加粗样式**

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

5.4 三种垃圾回收算法对比

在这里插入图片描述

6. 分代收集策略

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7. 增量收集算法

P152
在这里插入图片描述

8. 分区算法G1回收器

P153
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值