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

这篇具有很好参考价值的文章主要介绍了引用计数 vs 根可达算法:深入比较对象存活判定。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引用计数 vs 根可达算法:深入比较对象存活判定,# JVM,算法,java,开发语言

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:JVM
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

前言

在 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 到这个对象不可达时,则证明此对象是不可能再被使用的

引用计数 vs 根可达算法:深入比较对象存活判定,# JVM,算法,java,开发语言

如上图,在 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,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!文章来源地址https://www.toymoban.com/news/detail-522473.html

到了这里,关于引用计数 vs 根可达算法:深入比较对象存活判定的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java虚拟机(JVM):引用计数算法

    我们学习了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来就已知的,因此这几个区域

    2024年02月12日
    浏览(33)
  • JavaScript引用数据类型(对象类型)和原始(基本)数据类型特点比较

    为讲解JavaScript引用数据类型(对象类型)和原始(基本)数据类型特点比较,需要先回顾JavaScript数据类型有哪些? 一)原始(primitive:原始、基本)数据类型,也称为原始值(primitive value),包括: 1.布尔值(Boolean),其字面值只有两个,分别是true和false。 2.null,Null类型

    2024年02月05日
    浏览(39)
  • 深入探讨Docker生态系统,Docker Compose vs. Docker Swarm vs. Kubernetes:深入比较

    🎈个人主页:程序员 小侯 🎐CSDN新晋作者 🎉欢迎 👍点赞✍评论⭐收藏 ✨收录专栏:大数据系列 ✨文章内容:Docker生态系统 🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,大家一起学习交流!🤗 Docker已经成为现代容器化应用程序的事实标准,但

    2024年02月07日
    浏览(28)
  • 【排序算法】 计数排序(非比较排序)详解!了解哈希思想!

    🎥 屿小夏 : 个人主页 🔥个人专栏 : 算法—排序篇 🌄 莫道桑榆晚,为霞尚满天! 什么是计数排序?计数排序的思想是什么?它是如何实现的? 本文会对计数排序进行由浅入深的探究,让你彻底掌握计数排序! ​ 计数排序又称为鸽巢原理,是对哈希直接定址法的变形应

    2024年02月06日
    浏览(30)
  • 深入浅出排序算法之计数排序

    目录 1. 原理 2. 代码实现 3. 性能分析 首先看一个题目,有n个数,取值范围是 0~n,写出一个排序算法,要求时间复杂度和空间复杂度都是O(n)的。 为了达到这种效果,这一篇将会介绍一种 不基于比较的排序方法。 这种方法被称为计数排序。 计数排序的思路是这样的,对于每

    2024年02月06日
    浏览(26)
  • 关于VS2022使用EF生成实体模型报错的问题:运行转换:System.NullReferenceException:对象引用未设置为对象的示例。

    起因: 之前版本vs2022生成EF模型一直没有问题,在更新了最新的vs2022之后,版本号17.6+,出现此问题: 正在运行转换:System.NullReferenceException:未将对象引用设置到对象的实例。 具体错误如下: 正在运行转换: System.NullReferenceException: 未将对象引用设置到对象的实例。 在 Micro

    2024年02月08日
    浏览(40)
  • iOS——引用计数(一)

    自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术。 满足以下要求后,我们的代码就无需再次键入retain或者是release代码了: 使用Xcode 4.2或以上版本 使用LLVM编译器3.0或以上版本 编译器选项中设置ARC为有效 在以上条件下编译源代码时,编

    2024年02月07日
    浏览(35)
  • RCU安全引用计数

    原文网址:https://lwn.net/Articles/93617 原文作者:Corbet 原文时间:2004年7月14日 内核提供了一种用于实现引用计数的简单机制kref;该机制是今年3月份完成的。kref机制的核心思想是,提供支持原子操作的计数器,用于对未决引用【outstanding references】进行计数。如果计数器数值为

    2024年02月11日
    浏览(29)
  • C++引用计数

    引用计数(reference count)的核心思想是使用一个计数器来标识当前指针指向的对象被多少类的对象所使用(即记录指针指向对象被引用的次数)。它允许有多个相同值的对象共享这个值的实现。引用计数的使用常有两个目的: 简化跟踪堆中(也即C++中new出来的)的对象的过程

    2024年02月11日
    浏览(25)
  • C++智能指针学习——小谈引用计数

    目录 前言 控制块简介 共享控制块 引用计数与弱引用计数创建过程 __shared_ptr __shared_count _Sp_counted_base 弱引用计数增加过程 再谈共享控制块 __weak_count 引用计数增加过程 弱引用计数的减少过程 弱引用计数减为0 引用计数的减少过程 引用计数减为0 参考文章 本文结合源码讨论

    2024年04月08日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包