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://www.mzph.cn/diannao/47690.shtml

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

相关文章

Shell 构建flutter + Navtive 生成IPA

具体实现: #1. 在工程的根目录下,建立文件夹build_iOS文件,在此文件下建立build_iOS.sh的文件,把以下内容copy进sh文件; #2. 进入build_iOS.sh 文件的目录; #3. 在build_iOS 文件夹配置打包的DEVELOPExportOptionsPlist.plist(dev 构建)或AppStoreExportOptionsP…

SSR理解(vite与nuxt比较)

一、SSR的概念与理解(什么是SSR?) 定义:SSR是指在服务器端完成页面的渲染工作,将最终生成的HTML内容发送给浏览器。 简单来说,就是在服务器端将网页内容渲染成HTML,然后将这个渲染好的HTML发送到客户端,也就是我们的…

网络安全-等级保护制度介绍

一、等保发展历程 (1)1994国务院147号令 第一次提出等级保护概念,要求对信息系统分等级进行保护 (2)1999年GB17859 国家强制标准发布,信息系统等级保护必须遵循的法规 (3)2005年公安…

二、BIO、NIO、直接内存与零拷贝

一、网络通信编程基础 1、Socket Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是一组接口,由操作系统提供; Socket将复杂的TCP/IP协议处理和通信缓存管理都隐藏在接口后面,对用户来说就是使用简单的接口进行网络应用编程…

OPC UA边缘计算耦合器BL205工业通信的最佳解决方案

OPC UA耦合器BL205是钡铼技术基于下一代工业互联网技术推出的分布式、可插拔、结构紧凑、可编程的IO系统,可直接接入SCADA、MES、MOM、ERP等IT系统,无缝链接OT与IT层,是工业互联网、工业4.0、智能制造、数字化转型解决方案中IO系统最佳方案。…

Quartus II 13.1添加新的FPGA器件库

最近需要用到Altera的一款MAX II 系列EPM240的FPGA芯片,所以需要给我的Quartus II 13.1添加新的器件库,在此记录一下过程。 1 下载所需的期间库 进入Inter官网,(Altera已经被Inter收购)https://www.intel.cn/content…

error: linking with `cc` failed: exit status: 1

使用pip安装pyd4时报错,然后修改用手动安装 ./setup install 结果还是不行,报错 error: linking with cc failed: exit status: 1 error: cargo rustc --lib --message-formatjson-render-diagnostics --manifest-path Cargo.toml --release -v --fe…

web安全之SQL手工注入漏洞测试

一、目的 1.掌握SQL注入原理; Sql注入详解(原理篇)_sql注入攻击的原理-CSDN博客 2.了解手工注入的方法; 3.了解MySQL的数据结构; 4.了解字符串的MD5加解密 二、过程 1.进去后出现以下界面 找注入点 发现有注入点,即id被代入执…

华清数据结构day3 24-7-18

基于昨天代码增加增删改查功能 zy.h #ifndef ZY_H #define ZY_H #define MAX 100 //最大容量 //定义学生类型 struct Stu {char name[20];int age;double score; }; //定义班级类型 struct Class {struct Stu student[MAX]; //存放学生的容器int size; //实际…

udp和tcp区别

1. 连接性 TCP:TCP是一种面向连接的协议。在数据传输之前,TCP需要先建立连接,然后进行数据传输,最后再关闭连接。TCP通过三次握手来建立连接,确保数据的可靠性和完整性。一旦建立连接,数据传输过程中会进行…

Mysql —— 事务

目录 什么是事务? 两种方式实现事务: 方法一 方法二: 事务四大特性(简称ACID) 并发事务问题(面试题) 事务隔离级别 什么是事务? 事务是一组操作的集合,它是一个不可分割的工作单位&#xff…

封装的通用链表(list.c/list.h/test_list.c)

#ifndef LIST_H #define LIST_H#include <stdio.h> #include <stdbool.h>// 通用链表节点 typedef struct ListNode {void* ptr;struct ListNode* prev;struct ListNode* next; }ListNode;// 通用链表结构 typedef struct List {ListNode* head;size_t size; }List…

Web开发:ASP.NET CORE前后端交互之AJAX(含基础Demo)

目录 一、后端 二、前端 三、代码位置 四、实现效果 五、关键的点 1.后端传输给前端&#xff1a; 2.前端传输给后端 一、后端 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; using WebAppl…

【Android】Intent基础用法及作用

文章目录 使用Intent在活动中穿梭组成显式Intent隐式Intent显式与隐式区别作用 活动间传递数据向下一个活动传递数据返回数据给上一个活动 使用Intent在活动中穿梭 Intent&#xff08;意图&#xff09;是一种重要的消息传递对象&#xff0c;用于在不同组件&#xff08;如活动&…

数据挖掘新技能:Python爬虫编程指南

Python爬虫的优势 Python之所以成为数据爬取的首选语言&#xff0c;主要得益于其丰富的库和框架支持。以下是一些常用的库&#xff1a; Requests&#xff1a;用于发送HTTP请求&#xff0c;简单易用&#xff0c;是Python爬虫的基础库。BeautifulSoup&#xff1a;用于解析HTML文…

StarRocks on AWS Graviton3,实现 50% 以上性价比提升

在数据时代&#xff0c;企业拥有前所未有的大量数据资产&#xff0c;但如何从海量数据中发掘价值成为挑战。数据分析凭借强大的分析能力&#xff0c;可从不同维度挖掘数据中蕴含的见解和规律&#xff0c;为企业战略决策提供依据。数据分析在营销、风险管控、产品优化等领域发挥…

使用 spring MVC 简单的案例 (1)计算器

一、计算器 1.1前端代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> …

签名优化:请求数据类型不是`application/json`,将只对随机数进行签名计算,例如文件上传接口。

文章目录 I 签名进行请求数据类型类型判断1.1 常见的ContentType1.2 签名切面处理1.3 文件上传案例1.4 处理接口信息背景: 文件上传接口的请求数据类型通常为multipart/form-data,方便携带文本域和使用接口文档进行调试。 如果携带JSON数据,不方便调试接口。 前端数据也要特…

Github 2024-07-18 开源项目日报Top10

根据Github Trendings的统计,今日(2024-07-18统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量TypeScript项目3非开发语言项目3Jupyter Notebook项目2Python项目2JavaScript项目1C#项目1Rust项目1MDX项目1C++项目1项目化学习 创建周期:253…

Python数据获取(网页视频、音频版)

爬取数据&#xff0c;上一章有介绍&#xff0c;不懂流言私信或者评论交流即可&#xff0c; 在Python中编写爬虫通常涉及以下几个步骤&#xff1a; 发送HTTP请求&#xff1a;使用requests库向目标网站发送请求。解析网页内容&#xff1a;使用BeautifulSoup从HTML中解析出需要的…