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

一、引言

我们学习了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。

而Java堆和方法区这两个区域则有着很显著的不确定性:一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理,一般讨论“内存”分配与回收也仅仅特指这一部分。

二、引用计数算法

引用计数算法是一种内存管理算法,用于追踪对象的引用数量。它的基本原理是为每个对象维护一个计数器,记录当前有多少个指针指向该对象。当计数器的值变为0时,表示该对象不再被引用,可以被回收。

引用计数算法的实现思路如下:

  1. 在对象中添加一个引用计数器,初始值为0。
  2. 当有一个指针指向该对象时,引用计数器加1。
  3. 当一个指针不再指向该对象时,引用计数器减1。
  4. 当引用计数器的值为0时,表示没有指针指向该对象,可以将该对象回收。

引用计数算法的优点:

  1. 实时性:引用计数算法可以实时地进行内存回收,不需要等待垃圾回收器的运行。
  2. 简单高效:引用计数算法的实现相对简单,不需要遍历整个对象图,只需要维护计数器即可。

引用计数算法的缺点:

  1. 循环引用问题:当存在循环引用时,引用计数算法无法正确地回收内存。例如,对象A和对象B相互引用,它们的引用计数器都不会变为0,导致内存泄漏。
  2. 计数器更新开销:每次引用发生变化时,都需要更新计数器,导致额外的开销。

因为引用计数算法存在循环引用问题,所以现代的垃圾回收器往往不使用纯粹的引用计数算法,而是采用其他算法(如标记-清除算法、复制算法、标记-整理算法等)与引用计数算法结合,来解决循环引用的回收问题。

三、代码分析

以下是一个简单的引用计数算法的代码案例:

class ReferenceCounting {
    private int count; // 引用计数器
    public ReferenceCounting() {
        count = 0;
    }
    public void addReference() {
        count++;
    }
    public void removeReference() {
        count--;
    }
    public int getCount() {
        return count;
    }
}
class Object {
    private ReferenceCounting refCount; // 引用计数对象
    public Object() {
        refCount = new ReferenceCounting();
        refCount.addReference(); // 对象创建时增加引用计数
    }
    public void addReference() {
        refCount.addReference();
    }
    public void removeReference() {
        refCount.removeReference();
        if (refCount.getCount() == 0) {
            // 引用计数为0时执行回收操作
            System.out.println("Object is reclaimed.");
            // 执行回收操作
        }
    }
}
public class ReferenceCountingDemo {
    public static void main(String[] args) {
        Object obj1 = new Object(); // 创建对象1
        Object obj2 = new Object(); // 创建对象2
        obj1.addReference(); // obj1引用计数加1
        obj1.addReference(); // obj1引用计数加1
        obj2.addReference(); // obj2引用计数加1
        obj1.removeReference(); // obj1引用计数减1
        obj1.removeReference(); // obj1引用计数减1,计数为0,执行回收操作
        obj2.removeReference(); // obj2引用计数减1,计数不为0,不执行回收操作
    }
}

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

在下一个案例前,我们首先要学会在IDEA中输出gc日志信息:

循环引用代码分析:

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和B对象的引用
        a = null;
        b = null;
        // 这里无法回收A和B对象,因为它们之间存在循环引用
        System.gc();

    }
}

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

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

 控制台输出:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/89723.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 集成 WebSocket 实现服务端推送消息到客户端

WebSocket 简介 WebSocket 协议是基于 TCP 的一种新的网络协议,它实现了浏览器与服务器全双工(full-duplex)通信—允许服务器主动发送信息给客户端,这样就可以实现从客户端发送消息到服务器,而服务器又可以转发消息到客…

C语言易错点整理

前言: 本文涵盖了博主在平常写C语言题目时经常犯的一些错误,在这里帮大家整理出来,一些易错点会帮大家标识出来,希望大家看完这篇文章后有所得,引以为戒~ 一、 题目: 解答: 首先在这个程序中…

【LeetCode-中等题】73. 矩阵置零

题目 题解一&#xff1a;使用标记数组 public void setZeroes(int[][] matrix) {int m matrix.length;int n matrix[0].length;boolean[] row new boolean[m];boolean[] col new boolean[n];for(int i0; i< m;i){for(int j 0;j<n;j){if (matrix[i][j] 0) row[i]col…

GOLANG面向对象:封装 继承 多态

面向过程转换到面向对象&#xff0c;那么必然会涉及到几个特性&#xff1a;封装&#xff0c;继承&#xff0c;多态&#xff0c;那么Golang中的面向过程会有什么特性&#xff1f;那我们来仔细说一说&#xff1a; 封装&#xff1a; 首先要一个类的概念&#xff0c;类就像一下工厂…

springboot 基于JAVA的动漫周边商城的设计与实现64n21

动漫周边商城分为二个模块&#xff0c;分别是管理员功能模块和用户功能模块。管理员功能模块包括&#xff1a;文章资讯、文章类型、动漫活动、动漫商品功能&#xff0c;用户功能模块包括&#xff1a;文章资讯、动漫活动、动漫商品、购物车&#xff0c;传统的管理方式对时间、地…

《Java极简设计模式》第04章:建造者模式(Builder)

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 源码地址&#xff1a;https://github.com/binghe001/java-simple-design-patterns/tree/master/j…

LeetCode--HOT100题(40)

目录 题目描述&#xff1a;543. 二叉树的直径&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;543. 二叉树的直径&#xff08;简单&#xff09; 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最…

工厂方法模式介绍

韩敬海 设计模式&#xff08;Java版&#xff09; &#xff08;一&#xff09;定义 定义一个创建对象的接口&#xff0c;让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。 工厂方法涉及的角色有&#xff1a; 1 .抽象工厂角色&#xff1a;工厂方法模式的核心&am…

Android Studio实现解析HTML获取json,解析json图片URL,将URL存到list,进行瀑布流展示

目录 效果build.gradle&#xff08;app&#xff09;添加的依赖&#xff08;用不上的可以不加&#xff09;AndroidManifest.xml错误activity_main.xmlitem_image.xmlMainActivityImage适配器ImageModel 接收图片URL 效果 build.gradle&#xff08;app&#xff09;添加的依赖&…

win10 maven 安装环境变量设置不成功

maven 按照正常步骤设置环境变量 输入命令总是不能正常现实mvn的版本 解决方案: 1.删除掉设置的用户环境变量 2.将maven的完整目录写入系统变量path中 3.将该路径放到所有变量的最前面 4.点击确定,重新打开cmd 输入 mvn -v 正常了

Java——一个Java实体类,表示一个试题的模型

这段代码是一个Java实体类&#xff0c;表示一个试题的模型。 该实体类具有以下属性&#xff1a; id&#xff1a;题号&#xff0c;表示试题的编号。title&#xff1a;题目&#xff0c;表示试题的题目内容。optionA&#xff1a;选项A&#xff0c;表示试题的选项A。optionB&#…

如何通过tomcat下载映射下载文件

1.1找到tomcat服务器中server.xml文件 !--doBase是静态资源路径位置&#xff0c; path作用相当于设置的key, doBase作用相当于value --> <Context path"/download" docBase"E:\testBackData"></Context>1.2 找到tomcat服务器中web.xml文…

JAVA JNA 调用C接口的三种方式

文章目录 1. 准备一个共享库文件2. JNA姿势1—继承Library接口3. JNA姿势2—直接NativeLibrary.getInstance3. JNA姿势3—Native方法 1. 准备一个共享库文件 test.c #include <stdio.h> int test(char *input){printf("input:%s\n",input);return 0; }libtes…

Wlan安全——认证与加密方式(WPA/WPA2)

目录 终端认证技术 WEP认证 PSK认证 802.1x认证与MAC认证 Portal认证 数据加密技术 WEP加密 TKIP加密 CCMP加密 TKIP和CCMP生成密钥所需要的密钥信息 802.11安全标准 WEP共享密钥认证、加密工作原理 WEP共享密钥认证 WEP加解密过程 PSK认证以及生成动态密钥的工…

共创无线物联网数字化新模式|协创数据×企企通采购与供应链管理平台项目成功上线

近日&#xff0c;全球无线物联网领先者『协创数据技术股份有限公司』&#xff08;以下简称“协创数据”&#xff09;SRM采购与供应链项目全面上线&#xff0c;并于近日与企企通召开成功召开项目上线总结会。 基于双方资源和优势&#xff0c;共同打造了物联网特色的数字化采购供…

【C++】iota函数 + sort函数实现基于一个数组的多数组对应下标绑定排序

目录 一、iota函数 1. 函数解析 ​① 迭代器类型(补充) ② 头文件 ③ 参数 2. 函数用途与实例 二、sort函数 1、 函数解读 2、实现倒序排列 2.1 greater 与 less 模板参数 2.2 lambda表达式 三、下标绑定排序&#xff08;zip&#xff09; --- 833.字符串中的查找与替换 一、…

27- v-model 原理 组件应用

v-model 原理 原理: V-model本质上是一个语法糖。例如应用在输入框上&#xff0c;就是 value属性 和 input事件 的合写 作用: 提供数据的双向绑定 (1) 数据变,视图跟着变 : value (2) 试图变,数据跟着变: input 注意: $event 用于在模板中, 获取事件的形参 <template>…

RabbitMQ---基本消息模型

1、 基本消息模型 官方介绍&#xff1a; RabbitMQ是一个消息代理&#xff1a;它接受和转发消息。 你可以把它想象成一个邮局&#xff1a;当你把邮件放在邮箱里时&#xff0c;你可以确定邮差先生最终会把邮件发送给你的收件人。 在这个比喻中&#xff0c;RabbitMQ是邮政信箱&a…

无涯教程-PHP - Session选项

从PHP7 起&#xff0c; session_start()()函数接受一系列选项&#xff0c;以覆盖在 php.ini 中设置的会话配置指令。这些选项支持 session.lazy_write &#xff0c;默认情况下此函数为on&#xff0c;如果会话数据已更改&#xff0c;则会导致PHP覆盖任何会话文件。 添加的另一个…

【在Windows下搭建Tomcat HTTP服务】

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…