Garbage Collection -- 01 -- 判断对象是否存活

目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了 “自动化” 时代,那么为什么我们还要去了解 GC 和内存分配呢?答案很简单:当我们需要排查各种内存溢出、内存泄漏的问题时;当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些 “自动化” 的技术实施必要的监控和调节

  • 内存溢出 (out of memory):是指程序在申请内存时,没有足够的空间供其使用

  • 内存泄漏 (memory leak):是指程序在申请内存后,无法释放已申请的内存空间

  • 关于内存溢出和内存泄漏的区别,可以看这里 --> 内存溢出和内存泄漏的区别

Java 运行时数据区中的程序计数器、虚拟机栈、本地方法栈,这三个区域随线程而生,随线程而灭,当方法或线程结束的时候,这三个区域分配的内存也随着回收了。而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间的时候才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存


判断对象是否存活,主要用到了两种算法:引用计数算法和可达性分析算法

一、引用计数算法

  • 通过判断对象的引用数量来决定对象是否可以被回收

  • 每个对象实例都有一个引用计数器,当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;

  • 任何引用计数为 0 的对象实例都会被当作垃圾被 GC 回收

  • 优点

    • 执行效率高,程序执行受影响较小

    • 我们只需过滤出计数器值为0的对象,将其内存回收即可,可以交织在程序运行中

  • 缺点

    • 无法检测出循环引用的情况,导致内存泄漏

    • 例如:父对象有一个对子对象的引用,子对象又反过来引用父对象,这样它们的引用计数就永远不可能为 0

  • 示例

    public class MyObject {
    
        public MyObject childNode;
    }
    
    public class ReferenceCounterProblem {
    
        public static void main(String[] args) {
            MyObject object1 = new MyObject();
            MyObject object2 = new MyObject();
    
            object1.childNode = object2;
            object2.childNode = object1;
        }
    }
    
    • 如上所示,object1 中的 childNode 是通过 object2 来赋值的,object2 中的 childNode 是通过 object1 来赋值的,这就造成了两个对象之间的相互引用,也就是我们所说的循环引用

    • 因此目前主流的 Java 虚拟机里并没有选用引用计数器算法来管理内存


二、可达性分析算法

在这里插入图片描述

  • 通过判断对象的引用链是否可达来决定对象是否可以被回收

  • 可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作是一张图,通过一系列的称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链 (Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时 (用图论的话来说,就是 GC Roots 到这个对象不可达),则证明此对象是不可用的

  • 在 Java 中,可作为 GC Roots 的对象包括以下几种

    • 虚拟机栈 (栈帧中的本地变量表) 中引用的对象

      • 例如:我们在方法里 new 了一个对象,并赋值给了一个局部变量,那么在该局部变量没有被销毁之前,new 出的这个对象就会成为 GC Roots
    • 方法区中类静态属性引用的对象

      • 例如:在类中定义了一个静态属性,该静态属性保存的是某个对象的地址,那么被保存的对象就会成为 GC Roots
    • 方法区中常量引用的对象

      • 例如:在类中定义了一个常量,该常量保存的是某个对象的地址,那么被保存的对象就会成为 GC Roots
    • 本地方法栈中 JNI (即 Native 方法) 引用的对象

      • 我们调用 Native 方法,引用非 Java 语言构建的对象,也可以成为 GC Roots

三、归纳总结

  • 引用计数算法

    • 通过判断对象的引用数量来决定对象是否可以被回收

    • 每个对象实例都有一个引用计数器,当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;当一个对象的引用计数为0时,会被当做垃圾被GC回收

    • 优点

      • 执行效率高,程序执行受影响较小
    • 缺点

      • 无法检测出循环引用的情况,会导致内存泄漏
  • 可达性分析算法

    • 通过判断对象的引用链是否可达来决定对象是否可以被回收

    • 将"GC Roots"对象作为起始点,从这些节点开始向下搜索 (搜索走过的路径成为引用链),当一个对象到"GC Roots"没有任何引用链相连时,则证明此对象是不可用的

    • 可作为"GC Roots"的对象

      • 虚拟机栈 (栈帧中的局部变量表) 中引用的对象

      • 方法区中类静态属性引用的对象

      • 方法区中常量引用的对象

      • 本地方法栈中JNI (即Native方法) 引用的对象

发布了106 篇原创文章 · 获赞 83 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Goodbye_Youth/article/details/105389751