引用计数 vs 根可达算法:深入比较对象存活判定

简介: 引用计数 vs 根可达算法:深入比较对象存活判定

前言

在 Java 中,判定对象是否存活指的是哪些不再被程序所引用,也无法通过任何方式访问的对象;具体来说,当一个对象不再被任何活动线程所引用,并且没有被其他对象所引用时,它就被认为是 “死亡” 对象;“死亡” 对象占用内存空间但不再有任何实际的用途,因此需要通过垃圾收集机制将其从内存中释放,以便重新利用内存资源;Java 垃圾收集机制会自动识别、回收这些 “死亡” 对象,无需程序员手动管理内存释放的过程

什么是垃圾?

没有引用指向或根对象不可达的引用对象,被称之为垃圾

Java 与 C++ 对垃圾的处理方式有所不同,如下:

Java:自动回收垃圾,由 GC 回收机制去自动回收,开发效率高,执行效率低

C++:手动处理垃圾,若忘记回收时,容易发生内存泄漏,回收多次,会出现非法访问问题,一般手动回收的代码(delete)会写在析构函数中;开发效率低,执行效率高

如何定位垃圾

在堆里面存放着 Java 世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象哪些还 “存活” 着,哪些对象已经 “死亡” 了

引用计数算法

引用计数算法(Reference Counting):在对象中添加一个引用计数器,每当有一个其他对象引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的

引用指向一个对象,在它脑袋上写一个数字,有几个对象指向它就在它脑袋上写一个几,当这个数字变为 0 时,就说明没有任何对象指向它,即为 “垃圾”

在 Java 领域中,至少主流的 Java 虚拟机都没有选用引用计数算法来管理内存,主要原因:一个看似很简单的算法有很多例外情况要考虑,必须要配合大量额外处理才能正确地工作

譬如单纯的引用计数算法就很难解决对象之间相互引用的问题,例如:A->B、B->A,A、B 计数器的值都为 1,所以在当前算法来说不是垃圾,但从此看来,没有其他的引用会使用到它们,按理来说这几个都应该是为 “垃圾”

以上会出现的对象之间互相引用问题,通过代码来演示,看 Java JVM 中是否使用到了引用计数算法来回收垃圾,如下:

/**
 * @author vnjohn
 * @since 2023/6/29
 */
public class ReferenceCountingGC {
    public Object instance = null;
    private byte[] bigSize = new byte[2 * 1024 * 1024];
    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();
    }
}

调整 JVM Options 参数,增加打印 GC 回收详情信息,如下:

# 打印 GC 回收时间、GC 回收详情
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails

控制台打印结果如下:

0.107: [GC (System.gc()) [PSYoungGen: 6717K->608K(76288K)] 6717K->616K(251392K), 0.0044845 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
0.112: [Full GC (System.gc()) [PSYoungGen: 608K->0K(76288K)] [ParOldGen: 8K->378K(175104K)] 616K->378K(251392K), [Metaspace: 3100K->3100K(1056768K)], 0.0026966 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 76288K, used 3277K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
  eden space 65536K, 5% used [0x000000076ab00000,0x000000076ae334d8,0x000000076eb00000)
  from space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
  to   space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
 ParOldGen       total 175104K, used 378K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
  object space 175104K, 0% used [0x00000006c0000000,0x00000006c005e9e8,0x00000006cab00000)
 Metaspace       used 3130K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 343K, capacity 388K, committed 512K, reserved 1048576K

重点看前面两行日志信息

年轻代:0.107: [GC (System.gc()) [PSYoungGen: 6717K(回收前大小)->608K(回收后大小)(76288K)] 6717K->616K(251392K), 0.0044845 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

老年代:0.112: [Full GC (System.gc()) [PSYoungGen: 608K(回收前大小)->0K(回收后大小)(76288K)] [ParOldGen: 8K->378K(175104K)] 616K->378K(251392K), [Metaspace: 3100K->3100K(1056768K)], 0.0026966 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

以上标注黄色背景的日志内容,可以看出 Java 虚拟机并没有因为这两个对象循环引用而放弃回收它们,这也从侧面说明了 Java 虚拟机并不是通过引用计数算法来判断对象是否存活的

JDK 8 默认使用的垃圾收集器:Parallel Scavenge、Parallel Old

Java中常见的垃圾收集器,如:Serial、Parallel、CMS、G1 等,并不使用引用计数算法,而是采用基于可达性分析的算法来进行垃圾回收,这个也是后面要讲到的算法

可达性分析算法

可达性分析算法(Reachability Analysis):它是一个更广泛的概念,它是一类以可达性作为判断对象是否存活基础的算法,除了根可达算法外,还有其他的可达性分析算法,如:可达性分析与复制算法、可达性分析与标记-清除算法等

根可达算法是可达性分析算法中的一种具体实现方式,根可达算法是从一组称为 “GC Roots” 根对象开始,通过引用关系向下搜索,搜索过程所走过的过程称为 “引用链”;若某个对象到 GC Roots 间没有任何引用链相连或者用图论的方式来说就是从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的

如上图,在 Java 技术体系中,固定可作为 GC Roots 对象包括以下几种:

  1. 线程栈变量:在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等

比如:main 方法主线程开始运行,主线程栈中变量调用了其他的方法,主线程栈中的方法访问到的对象叫根对象

  1. 静态变量:在方法区类静态属性引用的对象,譬如 Java 类引用类型的静态变量

静态变量初始化,能够访问到的对象称之为根对象

  1. 常量池:在方法区中常量引用的对象,譬如字符串常量池(String Constant Pool)里面的引用

若一个 class 能够用到其他的 class 对象称之为根对象

  1. JNI 指针:在本地方法栈中 JNI(Java Native Interface)方法引用的对象
  2. 基础数据类型 Class 对象:Java 虚拟机内部的引用,如 int 类型对应 Class 对象是 Integer.TYPE 或 int.class、long 类型对应 Class 对象是 Long.TYPE 或 long.class
  3. 常驻异常对象:Java 虚拟机内部的引用,如 NullPointException、OutOfMemoryError
  4. 系统类加载器
  5. 同步锁持有对象:被同步锁 synchronized 持有的对象

GC Roots 是垃圾收集器判断对象是否存活的起点,不同的垃圾收集器会根据 GC Roots 选择合适的垃圾回收算法来进行垃圾回收、内存管理,它们的共同协作以确保内存的有效利用和程序的正常执行

常见的垃圾回收算法:复制(Copying)算法、标记-清除 (Mark-Sweep)算法、标记-整理(Mark-Compact)算法

至于很多人说还有分代算法,在我看来,分代模型应该是最准确的说法,分代算法不是指具体的一种算法,而是一种垃圾回收的策略或模型

由于对象的生命周期大部分是朝生夕死的,只有少数对象是长期存活的,基于此,垃圾收集器将堆内存划分为不同的代,分代模型将堆内存主要划分为新生代(Young Generation)和老年代(Old Generation)

G1 垃圾收集器逻辑分代,物理不分代

ZGC、Shenandoah 垃圾收集器没有物理分代,也没有逻辑分代

其他的垃圾收集器一般要么作用于新生代要么作用于老年代,例如:Parallel Scavenge-新生代、Parallel Old-老年代

关于垃圾回收算法、垃圾收集器后续文章见分晓,这里不过多展开~

总结

该篇博文讲解判定对象是否存活的条件通过什么方式去做的,引用计数器算法、根可达算法,在引用计数器算法中,通过简单的案例来演示在 Java 程序中并未通过该算法来判定对象是否存活,而是通过根可达算法去作判别的,罗列了 Java GC Roots 不同的种类,简要阐述了为下文作铺垫的垃圾回收算法、垃圾收集器,希望能先带你一起了解这方面的前置知识!

参考文献:《深入理解 Java 虚拟机》周志明著

博文放在 JVM 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!


目录
相关文章
|
算法 编译器 C++
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(中)
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(中)
71 0
|
7月前
|
数据可视化 算法 JavaScript
【Python数据挖掘】数据可视化及数据对象的相似性度量算法详解(超详细 附源码)
【Python数据挖掘】数据可视化及数据对象的相似性度量算法详解(超详细 附源码)
234 0
|
7月前
|
存储 监控 算法
垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程
垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程
100 0
|
存储 缓存 算法
JVM第三讲:深入理解java虚拟机之垃圾回收算法?CMS垃圾回收的基本流程?对象引用类型?
JVM第三讲:深入理解java虚拟机之垃圾回收算法?CMS垃圾回收的基本流程?对象引用类型?
244 0
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
79 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
5月前
|
存储 监控 算法
(六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解
经过前面五个章节的分析后,对于JVM的大部分子系统都已阐述完毕,在本文中则开始对JVM的GC子系统进行全面阐述,GC机制也是JVM的重中之重,调优、监控、面试都逃不开的JVM话题。
162 8
|
存储 算法 编译器
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(上)
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(上)
56 0
|
缓存 算法 安全
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(下)
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(下)
56 0
|
7月前
|
算法 数据可视化
R语言社区检测算法可视化网络图:ggplot2绘制igraph对象分析物种相对丰度
R语言社区检测算法可视化网络图:ggplot2绘制igraph对象分析物种相对丰度
|
7月前
|
缓存 算法 JavaScript
提高Java程序性能!了解可达性分析算法、强软弱虚引用和三色标记GC的过程,避免不可达对象阻碍程序性能!
提高Java程序性能!了解可达性分析算法、强软弱虚引用和三色标记GC的过程,避免不可达对象阻碍程序性能!
146 0
下一篇
DataWorks