• Java JVM垃圾回收确定垃圾的两种方式,GC Root


    前言

    对于Java两种确定对象为可回收的两种方式,予以记录!

    内存中已经不再被使用到的空间就是垃圾

    1. 引用计数法
    2. 根可达路径法

    一、引用计数法

    在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;

    任何时刻计数器为零的对象就是不可能再被使用的。那么这个对象就是可回收对象。

    但是,在Java领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存,主要原因是,这个看似简单的算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。

    在这里插入图片描述

    如图,每一个对象的引用都是1,构成了循环引用,但是并不能被其他对象访问,这两个对象再无任何引用,引用计数算法也就无法回收它们。

    1、引用计数代码示例

    在下列代码中,ReferenceCounting类是引用计数器类,用于记录对象被引用的次数。Object类是被引用的对象类,其中包含了一个ReferenceCounting对象。当创建对象时,引用计数加1,当移除对象引用时,引用计数减1。当引用计数为0时,表示对象不再被引用,可以执行回收操作。 在ReferenceCountingDemo类的main方法中,我们创建了两个对象obj1和obj2,分别增加和减少引用计数,演示了引用计数算法的基本原理

    public class ReferenceCountingGC {
        public static void main(String[] args) {
            Object obj1 = new Object();
            Object obj2 = new Object();
            obj1.addReference();// obj1引用计数加1, 当前为2
            obj2.addReference();// obj2引用计数加1, 当前为2
            obj1.removeReference();// obj1引用计数减1
            obj1.removeReference();// obj1引用计数减1, 计数为0,执行回收操作
            obj2.removeReference();// obj2引用计数减1, 计数不为0,不执行回收操作
        }
    }
    
    class Object {
        private ReferenceCounting refCount;
    
        public Object() {
            refCount = new ReferenceCounting();
            refCount.addReference();
        }
    
        public void addReference() {
            refCount.addReference();
            System.out.println("This Object " + this.hashCode() + ", refCount = " + refCount.getCount());
        }
    
        public void removeReference() {
            refCount.removeReference();
            System.out.println("This Object " + this.hashCode() + ", refCount = " + refCount.getCount());
            if (refCount.getCount() == 0) {
                System.out.println("This Object " + this.hashCode() + " is reclaimed.");
            }
        }
    }
    
    class ReferenceCounting {
        private int count; // 引用计数器
        public ReferenceCounting() {
            count = 0;
        }
        public void addReference() {
            count++;
        }
        public void removeReference() {
            count--;
        }
        public int getCount() {
            return count;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    在这里插入图片描述

    2、循环依赖问题代码示例

    在下述案例中,我们创建了两个类A和B,它们分别有一个成员变量用于相互引用。在main方法中,我们创建了一个A对象和一个B对象,并通过setB和setA方法将它们相互引用起来。但是,由于它们之间存在循环引用,即A对象引用B对象,B对象引用A对象,导致它们的引用计数器都不会变为0,无法被回收。

    尽管在最后我们将a和b设置为null解除了对它们的引用,但由于循环引用的存在,它们的引用计数仍然不为0,无法执行回收操作。

    class A {
        private B b;
        public void setB(B b) {
            this.b = b;
        }
    }
    
    class B {
        private A a;
    
        public void setA(A a) {
            this.a = a;
        }
    }
    
    public class ReferenceCountingDemo {
        public static void main(String[] args) {
            A a = new A();
            B b = new B();
            a.setB(b);
            b.setA(a);
            a = null;
            b = null;
            System.gc();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    从运行结果可以看到内存回收日志包含“Full GC (System.gc()) [PSYoungGen: 744K->0K(75264K)”,意味着虚拟机并没有因为这两个对象互相引用就放弃回收它们,这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。

    二、根可达路径法

    枚举根节点做可达性分析(根搜索路径)

    这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

    在这里插入图片描述

    在这里插入图片描述

    1、可作为GC Roots的六种对象

    1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
    2. 方法区中的常量引用的对象。譬如字符串常量池(String Table)里的引用。
    3. 本地方法栈中的JNI(native方法)引用的对象。
    4. Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
    5. 所有被同步锁(synchronized关键字)持有的对象。
    6. 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

    2、真正宣告对象是否可被回收需经过两次标记过程(重要)

    此对象到GCRoot对象没有引用链,此对象到GCRoot对象再也找不到一条可达路径

    在可达性分析法中不可达的对象,它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;

    1. 可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法当对象没有重写 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行垃圾回收。

    2. 被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

  • 相关阅读:
    一.计算机系统概述
    从协方差的角度详解线性判别分析原理+Python实现
    Maven插件初体验【附源码】
    JavaEE 线程状态及线程安全
    什么是P = NP?问题
    Spark读取elasticsearch数据指南
    竞赛选题 深度学习手势检测与识别算法 - opencv python
    网络机顶盒哪个好?达人分享最新网络电视机顶盒排名TOP5
    Web配置过滤器,Cookie对象的简单使用
    使用NATAPP内网穿透详细步骤
  • 原文地址:https://blog.csdn.net/qq_43357394/article/details/134099347