Java基础知识之 使用 Cleaner 替代 finalize

Object.finalize 方法

在 Java 中,一个对象如果不再使用,那么它就会在 JVM 垃圾回收时,进行析构释放该对象占用的内存空间。但如果这个对象持有了一些其他需要进行额外处理的资源(非堆内存资源),那么就得考虑这些资源的释放了。

在 Object 中,有一个方法 finalize,就是用来做这种操作的。这个方法会在对象被 GC 回收之前被 JVM 调用,一般都使用覆写这个函数来完成一些额外资源的清理工作,例如清理相关的 native 资源或是其他资源(socket、文件)的释放。

在这里推荐大家去看一下这篇文章,不仅讲解了 Object.finalize 方法,还讲解了 Object 中的其他所有方法:一文掌握 Object 类里的所有方法(wait、notify、finalize)

如果子类重写 finalize 这个方法,那么必须调用 super.finalize(),而且应该使用 try-finally 语法以确保始终调用 super.finalize()。下面就是一个使用 finalize 进行资源清理的例子:

class FinalizeObj {private long nativePointer;public FinalizeObj() {nativePointer = createNative();}@Overrideprotected void finalize() throws Throwable {try {    // 清理工作super.finalize();releaseNative(nativePointer);nativePointer = 0L;} finally {super.finalize();        }}private native long createNative();private native void releaseNative(long nativePointer);
}

这个例子中,在构造方法中创建了 native 底层资源,并在 finalize 方法中释放 native 底层资源。

当一个对象覆写 finalize 方法后,其回收的流程如下:

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了 finalize 方法,若未覆盖,则直接将其回收。否则,若对象未执行过 finalize 方法,将其移动到一个队列里,由一个低优先级线程执行该队列中对象的 finalize 方法。执行 finalize 方法完毕后,这些对象才成为真正的垃圾,等待下一轮垃圾回收。

但是现在想一想,这种进行资源清理的方式好吗?其实是不好的。例如程序员在覆写 finalize 方法时出现一些线程死锁的操作,那么就有可能造成垃圾回收的失败,同事也会产生严重的盐城阻塞问题。而且这个方法被执行的不确定性太大,一个对象从不可达到 finalize 方法被执行,完全依赖 JVM,这就无法保证被对象占用的资源被及时回收。

也是出于以上的原因,这个方法在 Java 9 中已经被标记为 Deprecated。那替代方案是啥呢?案是使用 java.lang.ref.Cleaner,这是 Java 9 推出的一个轻量级垃圾回收机制。

java.lang.ref.Cleaner

首先要说的是,这个类是 Java 9 的新特性,但是新特性往往都会带来繁琐的使用方式。但主要包含以下几个步骤

  1. 创建一个静态内部类继承 Runnable 作为进行清理的线程,在 run 方法中进行垃圾清理(必须使用 static 以避免持有外部实例的引用)
  2. 创建静态变量 cleaner 初始化为 Cleaner.create()
  3. 在构造方法中创建清理线程,并通过静态变量进行注册 cleaner.register,并保存返回的 cleanable
  4. 实现 AutoCloseable 接口,覆写 close 方法,在其中调用 cleanable.clean()

说起来比较麻烦,我们将上面那个例子改写一下,使用 Cleaner 来实现:

public class CleanerObj implements AutoCloseable {// 静态变量 cleaner 用于此类的实例的清理工作private static final Cleaner cleaner = Cleaner.create();// 静态内部类,清理线程,用于清理本类的实例private static class CleanRunnable implements Runnable {    private final long nativePointerForClean;CleanRunnable(long nativePointer) {//构造方法应该拿到用于清理的所有信息this.nativePointerForClean = nativePointer;}@Overridepublic void run() {//在此进行清理操作releaseNative(nativePointerForClean);}}private long nativePointer;                   // native资源指针private final CleanRunnable cleanRunnable;    // 此对象的垃圾回收线程private final Cleaner.Cleanable cleanable;    // 注册时返回的 cleanable 对象public CleanerObj() {//创建底层资源,保存返回的指针this.nativePointer = createNative();    //将底层资源的指针传递给清理线程对象,以便进行清理操作this.cleanRunnable = new CleanRunnable(this.nativePointer);//通过静态变量cleaner注册此对象和清理线程对象this.cleanable = cleaner.register(this, cleanRunnable);}@Overridepublic void close() {    //覆写AutoCloseable 的 close 方法cleanable.clean();    //调用 cleaner.register 返回的 cleanable 对象的 clean 方法}private native static long createNative();private native static void releaseNative(long nativePointer);
}

大家注意代码中的注解,已经解释的很清楚。这里再啰嗦一下,就是先创建属于类的静态变量 cleaner 和 清理线程,用于此类的对象的清理操作。而针对每一个对象,都需要通过这两个静态变量,创建清理线程并注册。最后在 AutoCloseableclose 方法中,调用 cleanable.clean()

实现 AutoCloseable 接口这使得此类在 try-finally 时能够自动调用 close 方法进行清理。如果未调用 close 方法,当这个对象不被任何地方引用时,Cleaner 也将调用清理操作。

我们先使用 try-finally 来进行测试:

try (CleanerObj obj = new CleanerObj()) {// do something with obj.
}
System.gc();

清理的流程如下:

System.out                I  createNative... thread = main
System.out                I  close... thread = main
System.out                I  CleanRunnable.run... thread = main
System.out                I  releaseNative... thread = main

当我们不使用 try-catch 时:

CleanerObj obj = new CleanerObj();
System.gc();

清理的流程如下:

System.out                I  createNative... thread = main
System.out                I  CleanRunnable.run... thread = Cleaner-0
System.out                I  releaseNative... thread = Cleaner-0

可以看到,两种方式最终都会调用到 releaseNative 方法进行垃圾清理,而不同的仅仅是垃圾处理的线程而已。

相比于 finalize 方法,使用 Cleaner 进行清理操作实在是太麻烦了。但这也是没办法的事情,随着后续 Java 技术的发展,很多传统类结构的设计一定要有所改变,因为现在的程序编写得越来越庞大,同时业务的繁琐程度也越来越高,以及硬件和网络通讯水平的不断发展,程序语言内部的结构升级必然是整个行业的趋势。

最后,这里通过一张图来演示 Java 中对象的生命周期,并结束本篇文章,祝大家升职加薪。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

JavaEE:Lombok工具包的使用以及EditStarter插件的安装

Lombok是一个Java工具库&#xff0c;通过添加注解的方式&#xff0c;简化Java的开发。 目录 1、引入依赖 2、使用 3、原理解释 4、更多使用 5、更快捷的引入依赖 1、引入依赖 <dependency><groupId>org.projectlombok</groupId><artifactId>lomb…

Docker 镜像使用和安装

​ 1、简介 Docker是一个开源的应用容器引擎&#xff1b;是一个轻量级容器技术&#xff1b; Docker支持将软件编译成一个镜像&#xff1b;然后在镜像中各种软件做好配置&#xff0c;将镜像发布出去&#xff0c;其他使用者可以直接使用这个镜像&#xff1b; 运行中的这个镜像…

【代码随想录】【算法训练营】【第58天 2】 [卡码102]沉没孤岛

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 卡码网。 day 58&#xff0c;周四&#xff0c;ding~ 题目详情 [卡码102] 沉没孤岛 题目描述 卡码102 沉没孤岛 解题思路 前提&#xff1a;修改孤岛的值 思路&#xff1a;DFS or BFS&#xff0c;使用visite…

全时守护,无死角监测:重点海域渔港视频AI智能监管方案

一、方案背景 随着海洋经济的快速发展和海洋资源的日益紧缺&#xff0c;对重点海域渔港进行有效监控和管理显得尤为重要。视频监控作为一种高效、实时的管理手段&#xff0c;已成为渔港管理中不可或缺的一部分。当前&#xff0c;我国海域面积广阔&#xff0c;渔港众多&#xf…

Chromium CI/CD 之Jenkins实用指南2024 - 常见的构建错误(六)

1. 引言 在前一篇《Chromium CI/CD 之 Jenkins - 发送任务到Ubuntu&#xff08;五&#xff09;》中&#xff0c;我们详细讲解了如何将Jenkins任务发送到Ubuntu节点执行&#xff0c;并成功验证了文件的传输和回传。这些操作帮助您充分利用远程节点资源&#xff0c;提升了构建和…

ROS、pix4、gazebo、qgc仿真ubuntu20.04

一、ubuntu、ros安装教程比较多&#xff0c;此文章不做详细讲解。该文章基于ubuntu20.04系统。 pix4参考地址&#xff1a;https://docs.px4.io/main/zh/index.html 二、安装pix4 1. git clone https://github.com/PX4/PX4-Autopilot.git --recursive 2. bash ./PX4-Autopilot…

【20】读感 - 架构整洁之道(二)

概述 继上一篇文章讲了前两章的读感&#xff0c;已经归纳总结的重点&#xff0c;这章会继续跟进的看一下&#xff0c;深挖架构整洁之道。 编程范式 编程范式从早期到至今&#xff0c;提过哪些编程范式&#xff0c;结构化编程&#xff0c;面向对象编程&#xff0c;函数式编程…

秋招突击——7/17——复习{二分查找——搜索插入位置、搜索二维矩阵,}——新作{链表——反转链表和回文链表,子串——和为K的子数组}

文章目录 引言新作二分模板二分查找——搜索插入位置复习实现 搜索二维矩阵复习实现 新作反转链表个人实现参考实现 回文链表个人实现参考实现 和为K的子数组个人实现参考实现 总结 引言 今天算法得是速通的&#xff0c;严格把控好时间&#xff0c;后面要准备去面试提前批了&a…

怎样在 PostgreSQL 中进行用户权限的精细管理?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中进行用户权限的精细管理&#xff1f;一、权限管理的重要性二、PostgreSQL 中的权…

Odoo17架构概述

多层架构 Odoo遵循多层架构&#xff0c;这意味着演示&#xff0c;业务逻辑和数据存储是分开的。更具体地说&#xff0c;它使用三层架构。 UI展示层 UI表示层是 HTML5、JavaScript 和 CSS 的组合。 应用程序的最顶层是用户界面。界面的主要功能是将任务和结果转换为用户可以理…

Nginx详解(超级详细)

目录 Nginx简介 1. 为什么使用Nginx 2. 安装Nginx Nginx的核心功能 1. Nginx反向代理功能 2. Nginx的负载均衡 3 Nginx动静分离 Nginx简介 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在BSD-like 协…

JavaScript 获取 url(get)参数

https://andi.cn/page/621584.html

ubuntu2204配置anacondacuda4090nvidia驱动

背景 某个机房的几台机器前段时间通过dnat暴露至公网后被入侵挖矿&#xff0c;为避免一些安全隐患将这几台机器执行重装系统操作&#xff1b; 这里主要记录配置nvidia驱动及cuda&anaconda。 步骤 大概分为几个步骤 禁用nouveau配置grub显示菜单install nvidia-driveri…

Linux-mysql数据备份恢复

MySQL数据备份与恢复 一、备份介绍 1、为什么要备份 备份&#xff1a;能够防止由于机械故障以及人为误操作带来的数据丢失&#xff0c;例如将数据库文件保存在了其它地方。 冗余&#xff1a; 数据有多份冗余&#xff0c;但不等备份&#xff0c;只能防止机械故障带来的数据丢…

怎样在 PostgreSQL 中优化对大表的分区裁剪和索引选择?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中优化对大表的分区裁剪和索引选择一、分区裁剪&#xff1a;精准切割&#xff0c;提…

RocketMQ单结点安装/Dashboard安装

目录 1.安装NameServer 2.安装Broker 3.使用自带工具测试数据发送 4.使用DashBoard进行查看 5.关闭相关设备 前置条件&#xff1a;两台虚拟机CentOS Linux release 7.5.1804(ps:当然也可以都部署在一台机器上) RocketMq属于天生集群。需要同时启动nameServer和Broker进行…

SpringBoot常用功能实现

1. 配置文件多环境配置 1.1 创建不同环境配置文件 文件名前缀和后缀为标准固定格式&#xff0c;不可以改变。 1.2 pom中加入文件配置 <profiles><profile><id>dev</id><properties><profile.name>dev</profile.name></propert…

ABAP使用SQL直接更新数据库与使用IN UPDATE TASK的区别

1. 背景 刚接触ABAP的小伙伴常常会有这样的疑问&#xff0c;为什么不直接使用Open SQL直接更新数据库&#xff0c;而要把对DB的操作封装到IN UPDATE TASK中呢&#xff1f; 对于这个问题&#xff0c;比较常见的解释是&#xff0c;IN UPDATE TASK的方式会保证数据更新的一致性。…

AI 模型本地推理 - YYPOLOE - Python - Windows - GPU - 吸烟检测(目标检测)- 有配套资源直接上手实现

Python 运行 - GPU 推理 - windows 环境准备python 代码 环境准备 FastDeploy预编译库下载 conda config --add channels conda-forge && conda install cudatoolkit11.2 cudnn8.2 pip install fastdeploy_gpu_python-0.0.0-cp38-cp38-win_amd64.whlpython 代码 impo…

2024算力基础设施安全架构设计与思考(免费下载)

算网安全体系是将数据中心集群、算力枢纽、一体化大数据中心三个层级的安全需求进行工程化解耦&#xff0c;从国家安全角度统筹设计&#xff0c;通过安全 服务化方式&#xff0c;依托威胁情报和指挥协同通道将三层四级安全体系串联贯通&#xff0c;达成一体化大数据安全目标。 …