Java高效编程(7):消除过时的对象引用

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界

在从手动管理内存的语言(如C或C++)转向垃圾回收语言(如Java)时,程序员的工作变得容易得多,因为对象在不再使用时会被自动回收。然而,这种自动回收机制并不意味着程序员可以完全忽视内存管理。实际上,错误地保留对象引用(即过时引用)可能导致严重的内存泄漏问题。尽管Java具备垃圾回收功能,但程序员依然需要对内存管理保持警惕。

内存泄漏的隐患

考虑下面这个简单的栈实现:

// 存在"内存泄漏"的问题
public class SimpleStack {private Object[] elements;private int size = 0;private static final int DEFAULT_CAPACITY = 16;public SimpleStack() {elements = new Object[DEFAULT_CAPACITY];}public void push(Object item) {ensureCapacity();elements[size++] = item;}public Object pop() {if (size == 0) throw new EmptyStackException();return elements[--size];}private void ensureCapacity() {if (elements.length == size) {elements = Arrays.copyOf(elements, 2 * size + 1);}}
}

表面上,这段代码似乎没有问题,甚至可以通过所有的测试,但实际上它存在一个隐蔽的内存泄漏。当栈增长并随后缩小时,被弹出的元素不会被垃圾回收,因为这些弹出的对象引用依然保留在 elements 数组中。这些引用已不再需要,但依然存在,形成了“过时的对象引用”。

过时的对象引用是指那些程序永远不会再引用的对象。栈中 size 以下的元素仍然有效,而 size 以上的元素则已经无效,应该被垃圾回收。然而,Java 的垃圾回收器并不清楚这些无效的对象引用,认为它们仍然有效。结果是,栈类的内存使用逐渐增加,影响性能,甚至可能导致 OutOfMemoryError 异常。

解决方法:手动清理过时引用

为了解决这个问题,应该在弹出元素时将对应的数组位置置为 null。如下所示:

public Object pop() {if (size == 0) throw new EmptyStackException();Object result = elements[--size];elements[size] = null; // 清除过时的对象引用return result;
}

通过将弹出的元素位置置为 null,程序显式告诉垃圾回收器,这些对象引用已经无效,可以被回收。这样不仅提高了内存管理效率,还增加了程序的健壮性。如果将来的代码误引用了这些已清理的对象引用,会立即抛出 NullPointerException,而不是导致难以察觉的错误。

何时应该清理对象引用

虽然在本例中需要手动清理引用,但这并不意味着每个对象引用都需要立即清理。滥用 null 赋值会让代码变得杂乱无章,不利于维护。一般来说,只有当类自己管理内存时(例如栈类),才需要主动清除过时引用。对于大多数情况下,定义变量时将它们的作用范围限制在最窄的作用域(详见【条目57】),变量在离开作用域后会自动消失,不再需要显式地将其置为 null

其他内存泄漏来源

缓存

缓存是另一个常见的内存泄漏来源。一旦将对象引用放入缓存中,程序员很容易忘记它的存在,导致对象在缓存中长期驻留,即使它们已不再有用。一个解决方案是使用 WeakHashMap 来表示缓存,当键的外部引用消失时,缓存条目会自动被移除。WeakHashMap 只适用于缓存条目生命周期由键的外部引用决定的情况。

在更多情况下,缓存条目的生命周期并不固定,条目会随着时间的推移变得不再有价值。这时可以通过定期清理缓存来避免内存泄漏。这种清理可以由后台线程执行(例如使用 ScheduledThreadPoolExecutor),也可以作为添加新条目时的副作用。LinkedHashMap 提供了 removeEldestEntry 方法来支持这种机制。如果需要更复杂的缓存管理,可能需要直接使用 java.lang.ref 类进行控制。

监听器和回调

监听器和回调机制也是常见的内存泄漏来源。当客户端注册了回调而没有显式地取消注册时,这些回调对象可能会不断累积。一个解决方案是只存储它们的弱引用,例如使用 WeakHashMap 来存储回调。当没有其他外部引用时,回调对象会自动被垃圾回收。

如何发现内存泄漏

内存泄漏通常不会表现为显而易见的错误,它们可能在系统中存在多年,直到系统性能出现显著下降。一般情况下,内存泄漏是通过仔细的代码审查或借助堆内存分析工具(如 heap profiler)才得以发现。因此,预见这些问题并在编码时主动避免它们,是非常值得学习的技能。

总结

尽管 Java 具备垃圾回收机制,但程序员仍需要对内存管理保持警觉,特别是当类管理自己的内存时。通过及时清理过时的对象引用,可以防止内存泄漏,避免性能下降和内存溢出等问题。常见的内存泄漏来源包括栈、缓存和回调机制,使用弱引用、定期清理或缩小变量作用域都是有效的解决方案。内存管理不仅影响性能,也关乎代码的长期稳定性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图解C#高级教程(三):泛型

本讲用许多代码示例介绍了 C# 语言当中的泛型,主要包括泛型类、接口、结构、委托和方法。 文章目录 1. 为什么需要泛型?2. 泛型类的定义2.1 泛型类的定义2.2 使用泛型类创建变量和实例 3. 使用泛型类实现一个简单的栈3.1 类型参数的约束3.2 Where 子句3…

安装图片标识工具anylabeling

目录 下载压缩包 创建环境 安装opencv 安装第三方库 运行setup.py文件 安装过程可能会出现的错误: 错误1 错误2 安装完成 图标更换 之前提到的嵌入式开发】可编程4k蓝牙摄像头点击器还可以训练模型,使图像识别精度提高 现在讲解,如…

uniapp微信小程序,获取上一页面路由

在进入当前页面的时候,判断是不是从某个页面跳转过来的(一般是当前页面为公共页面是出现的),比如 A-->B C-->B ,那么 要在 C跳转到B页面的时候多个提示语什么的 而在A跳转到B时不需要,那么就要判断 上一页面的…

前端规范工程-5:Git提交信息规范(commitlint + czg)

前面讲的都是在git提交之前的一些检查流程,然而我们git提交信息的时候,也应该是需要规范的。直接进入主题: 目录 需安装插件清单commitlint 介绍安装配置配置commit-msg钩子提交填写commit信息czg后续方式一:push触动build并上传…

DataEase v2 开源代码 Windows 从0到1环境搭建

一、环境准备 功能名称 描述 其它 操作系统 Windows 数据库 Mysql8.0 开发环境 JDK17以上 本项基于的21版本开发 Maven 3.9版本 开发工具 idea2024.2版本 前端 VSCode TIPS:如果你本地有jdk8版本,需要切换21版本,请看…

深入浅出MySQL事务处理:从基础概念到ACID特性及并发控制

1、什么是事务 在实际的业务开发中,有些业务操作要多次访问数据库。一个业务要发送多条SQL语句给数据库执行。需要将多次访问数据库的操作视为一个整体来执行,要么所有的SQL语句全部执行成功。如果其中有一条SQL语句失败,就进行事务的回滚&a…

RabbitMQ的应用问题

一、幂等性保障 幂等性是数学和计算机科学中某些运算的性质, 它们可以被多次应⽤, ⽽不会改变初始应⽤的结果 数学上的幂等性: f(x)f(f(x)) |x| 数据库操作幂等性: 数据库的 select 操作. 不同时间两次查询的结果可能不同, 但是这个操作是符合幂等性…

教务系统登录的分析

武汉纺织大学屏蔽了正方教务系统的默认登录页面,他们学校自定义的登录页面用户名和密码都是明文传输。可以使用Httpclient模拟登录。手动登录后,5次get请求才能获得真实的cookies。合肥工业大学需要3次。 第一次是POST请求。 Post请求的的下一个Location…

yum使用阿里云的镜像源报错 Failed connect to mirrors.aliyuncs.com:80; Connection refused“

报错:Failed connect to mirrors.aliyuncs.com:80; Connection refused",如果单独只是这个报错的话,那么原因是由于非阿里云ECS用户无法解析主机“mirrors.cloud.aliyuncs.com”。如果不单单只是这个报错另外还有其它报错请参考我其它文…

【SQL】筛选字符串与正则表达式

目录 语法 需求 示例 分析 代码 语法 SELECT column1, column2, ... FROM table_name WHERE condition; WHERE 子句用于指定过滤条件,以限制从数据库表中检索的数据。当你执行一个查询时,WHERE 子句允许你筛选出满足特定条件的记录。如果记录满…

[RabbitMQ] 7种工作模式详细介绍

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…

Android Studio 新版本 Logcat 的使用详解

点击进入官方Logcat介绍 一个好的Android程序员要会使用AndroidStudio自带的Logcat查看日志,会Log定位也是查找程序bug的第一关键。同时Logcat是一个查看和处理日志消息的工具,它可以更快的帮助开发者调试应用程序。 步入正题,看图说话。 点…

特征工程——一门提高机器学习性能的艺术

当前围绕人工智能(AI)和机器学习(ML)展开的许多讨论以模型为中心,聚焦于 ML和深度学习(DL)的最新进展。这种模型优先的方法往往对用于训练这些模型的数据关注不足,甚至完全忽视。类似MLOps的领域正迅速发展,通过系统性地训练和利用ML模型&…

Hive SQL业务场景:连续5天涨幅超过5%股票

一、需求描述 现有一张股票价格表 dwd_stock_trade_dtl 有3个字段分别是: 股票代码(stock_code), 日期(trade_date), 收盘价格(closing_price) 。 请找出满足连续5天以上(含)每天上涨超过5%的股票,并给出连续满足…

C++入门基础知识93(实例)——实例18【猴子吃桃问题】

成长路上不孤单😊😊😊😊😊😊 【14后😊///C爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于猴子吃桃问题的相关内容! 关…

IP协议讲解

IP协议 IP协议的本质:提供一种能力,将数据跨网络从A主机传输到B主机 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4. 4位头部长度(header length): IP头部的长度是多少个32bit, 也就是 length * 4 的字节数. 4bit表示最大 的数字是15, 因…

天坑!Spark+Hive+Paimon+Dolphinscheduler

背景: 数据中台项目使用Spark+Hive+Paimon做湖仓底层,调度任务使用的是基于Dolphinscheduler进行二开。在做离线脚本任务开发时,在Paimon库下执行非查询类SQL报错。 INSERT报错 DELETE报错 现状: 原始逻辑为数据中台中选择的Paimon数据源,实际上在Dolphinscheduler中是…

视频集成与融合项目中需要视频编码,但是分辨率不兼容怎么办?

在众多视频整合项目中,一个显著的趋势是融合多元化的视频资源,以实现统一监管与灵活调度。这一需求促使项目团队不断探索新的集成方案,确保不同来源的视频流能够无缝对接,共同服务于统一的调看与管理平台,进而提升整体…

TI DSP TMS320F280025 Note13:CPUtimer定时器原理分析与使用

TMS320F280025 CPUtimer定时器原理分析与使用 ` 文章目录 TMS320F280025 CPUtimer定时器原理分析与使用框图分析定时器中断定时器使用CPUtimers.cCPUtimers.h框图分析 定时器框图如图所示 定时器有一个预分频模块和一个定时/计数模块, 其中预分频模块包括一个 16 位的定时器分…

【机器学习基础】Transformer学习

Transformer学习 梯度消失FeedForward层激活函数的主要作用是在网络中加入非线性变换 梯度消失 梯度爆炸 FeedForward层 Transformer结构: Transformer结构主要分为两大部分: 一是Encoder层结构:Encoder 的输入由 Input Embedding 和 Positional Embedding 求和输入Multi…