资源管理规范

踩坑经验:

对于IO及池化资源(文件、线程池、网络IO(HttpClient)、磁盘IO),使用之后一定要及时回收,好借好还,再借不难。

稳定关闭方式

  1. 完成I/O操作后,应该关闭流以释放系统资源。可以使用finally块确保流被关闭,或者使用try-with-resources语句自动关闭。
FileInputStream fis = null;
try {
    fis = new FileInputStream("file.txt");
    // 操作文件
} catch (IOException e) {
    // 处理异常
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            // 处理关闭时的异常
        }
    }
}

       2. java 7及以上版本支持try-with-resources语句,它可以自动关闭实现了AutoCloseable接口的资源。

     try-with-resources模式不仅适用于文件操作,还可以用于数据库连接、网络连接等任何需要清理资源的场景。

try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    // 操作文件
}
// reader在这里会自动关闭

节约资源

  1. 避免循环开启和关闭。在循环外部打开资源,然后在循环内部使用,这样可以避免在每次迭代中都打开和关闭资源。
  2. 使用连接池
    • 对于数据库连接、线程处理异步任务,使用连接池来管理,而不是每次操作都创建和关闭连接。
  3. 使用线程局部变量(ThreadLocal)时要小心
    • ThreadLocal变量可能会在线程结束后仍然持有资源,导致资源泄露。确保在适当的时候移除或释放这些资源。mvc请求或者jsf请求可以在拦截器中统一进行设置上下文,执行结束后统一移除。
  4. 释放外部资源
    • 如果你的应用使用到了外部资源,如网络连接、文件句柄等,确保在不再需要时释放它们。
  5. 避免使用全局变量持有资源引用
    • 全局变量可能会长时间持有资源引用,导致资源无法被垃圾回收。
  6. 使用对象池
    • 对于频繁创建和销毁的对象,可以使用对象池来减少资源消耗。
  7. 注销监听器和其他回调
    • 当不再需要监听器或其他回调时,确保注销它们,以避免内存泄露。
  8. 使用适当的设计模式
    • 某些设计模式,如单例(Singleton)或静态持有(Static Hold),可能会隐含地持有资源引用。使用这些模式时要注意资源的生命周期。
  9. 定期检查资源使用情况
    • 通过泰山磁盘、内存监控定期检查资源使用情况,以便发现潜在的资源泄露。
  10. 编写单元测试
    • 编写单元测试来验证资源是否在适当的时候被释放。
  11. 避免在异常处理中创建资源
    • catch块或finally块中创建资源可能会导致资源无法被正确关闭。
  12. 使用资源管理框架
    • 考虑使用资源管理框架,如Apache Commons IO的IOUtils,它们提供了方便的方法来关闭资源。
  13. 理解垃圾回收机制
    • 了解Java的垃圾回收机制,确保资源对象的引用能够被垃圾回收器正确回收。
  14. 避免使用finalize方法
    • finalize方法在对象被垃圾回收时调用,它的执行时机不确定,不应该依赖它来释放资源。

通过遵循这些规范,可以有效地防止资源泄露,提高应用的稳定性和性能。

示例

  1. 线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
//线程池类未继承closeable接口,需要手动进行关闭
public class ThreadPoolShutdown {

    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
            
        try {
            // 提交任务到线程池
            executorService.submit(() -> {
                // 模拟任务执行
                System.out.println("Task is running.");
            });
        } catch (InterruptedException e) {
            log.error("失败",e);
        }finally{
            //不能继续提交任务,现有任务可以执行完成
            executorService.shutdown();
            // 尝试立即停止所有正在执行的任务,并暂停处理等待的任务,并返回等待执行的任务列表
            // 此方法不会等待正在执行的任务完成
            executorService.shutdownNow();
        }
    }
}

   2.io流

import java.io.FileInputStream;
import java.io.IOException;

public class ReadFile {
    public static void main(String[] args) {
        String filePath = "example.txt";
        byte[] bytes = new byte[1024];
        int bytesRead;

        try (FileInputStream fis = new FileInputStream(filePath)) {
            while ((bytesRead = fis.read(bytes)) != -1) {
                // 将读取的字节转换为字符串
                String readData = new String(bytes, 0, bytesRead);
                System.out.println(readData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public void read() {
        String content = "Hello, World!";
        String filePath = "example.txt";

        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            // 将字符串转换为字节并写入文件
            fos.write(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //字符流方式读取
    public void readByBuffer(){
        String filePath = "example.txt";
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //字符流方式写入
   public  void writeByBuffer() {
        String content = "Hello, World!";
        String filePath = "example.txt";

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

   3.Apache CSV

public static void main(String[] args) {
        
    //支持多个资源
    try (Reader fileReader = new InputStreamReader(new FileInputStream("filename"), Charset.forName("utf-8"));
        CSVParser records = CSVFormat.DEFAULT.withQuoteMode(QuoteMode.ALL_NON_NULL).withFirstRecordAsHeader()
            .withAllowMissingColumnNames().parse(fileReader)) {

        for (CSVRecord record : records) {
            log.info("记录:{}", record.get("iemi"));
        }

    } catch (Exception e) {
        log.error("错误", e);
    }

}

一般fileReader和records是全局的

 protected Reader fileReader;
    protected CSVParser records;
  @SneakyThrows
    public Long export(File file, String[] properties, String[] headers) {
        reset();
        PrintWriter printWriter = new PrintWriter(file, StandardCharsets.UTF_8.name());
        CSVPrinter csvPrinter = CSVFormat.EXCEL.withHeader(headers).print(printWriter);
        Long rows = 0L;
        try {
            for (CSVRecord record : records) {
                List<String> list = toList(record, properties);
                if (list != null) {
                    csvPrinter.printRecord(escapeValues(list));
                    rows++;
                }
            }
        } catch (Exception e) {
            log.error("csv standard exception...{}", e.getMessage());
            throw e;
        } finally {
            close();
            csvPrinter.close(true);//包含刷新
            printWriter.close();
        }
        return rows;
    }


    public void close() {
        if (records != null) {
            records.close();
        }
        if (fileReader != null) {
            fileReader.close();
        }
    }

  1. 网络io流

        Httpclient相关

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import java.io.IOException;
//httpclient连接池不能自动关闭,CloseableHttpClient 实现了Closeable
//RestTemplate 帮助使用者关闭了
public class HttpClientShutdown {
    public static void main(String[] args) {
        // 创建HttpClient连接池管理器
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();

        // 创建CloseableHttpClient实例
        try (CloseableHttpClient client = HttpClients.custom()
                .setConnectionManager(cm)
                .build()) {
            // 使用client执行HTTP请求
        }finally{
            // 关闭HttpClient连接池
            // 这一步很重要,因为HttpClient默认不会自动关闭连接池
            cm.shutdown();
        }      
    }
}

其中,线程池ExecutorService、HttpClient连接池都需要进行资源回收,需调用shutdown方法

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NioSocketChannelCloseExample {
    public static void main(String[] args) {
        String host = "example.com"; // 替换为实际的服务器地址
        int port = 12345; // 替换为实际的服务器端口

        try (Socket socket = new Socket();
             SocketChannel socketChannel = socket.getChannel()) {

            // 连接到服务器
            socketChannel.connect(new InetSocketAddress(host, port));

            // 创建ByteBuffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // 从SocketChannel读取数据到ByteBuffer
            int bytesRead = socketChannel.read(buffer);
            if (bytesRead == -1) {
                // 达到对端流的末尾
                return;
            }

            // 切换ByteBuffer到读模式
            buffer.flip();

            // 处理接收到的数据
            // 例如,将字节转换为字符串
            byte[] data = new byte[buffer.limit()];
            buffer.get(data);
            String receivedString = new String(data, 0, bytesRead);
            System.out.println("Received: " + receivedString);

            // 发送数据到SocketChannel
            String message = "Hello, Server!";
            buffer.clear();
            buffer.put(message.getBytes());
            buffer.flip();
            while (buffer.hasRemaining()) {
                socketChannel.write(buffer);
            }

            // 在这个try-with-resources块结束时,Socket和SocketChannel都会自动关闭
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 所有资源都已在try-with-resources语句中自动关闭
    }
}
  1. ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<>();

try {
    threadLocal.set("some value");
    // 执行一些操作
} finally {
    //结束后remove
    threadLocal.remove();
}
public class ThreadLocalWithCleanup extends ThreadLocal<String> {
    @Override
    protected String initialValue() {
        return "default value";
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        this.remove(); // 确保在对象被垃圾回收前移除值
    }
}
public class Task implements Runnable {
    private final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public Task() {
        // 初始化ThreadLocal
    }

    @Override
    public void run() {
        threadLocal.set("some value");
        try {
            // 执行任务
        } finally {
            threadLocal.remove(); // 任务执行完毕后移除值
        }
    }
}

某些场景下,子线程需要使用父线程的上下文

public class ParentTask implements Runnable {
    public void run() {
        InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
        threadLocal.set("parent value");

        // 创建并启动子线程
        Thread childThread = new Thread(new ChildTask(threadLocal));
        childThread.start();
    }
}

public class ChildTask implements Runnable {
    private final InheritableThreadLocal<String> threadLocal;

    public ChildTask(InheritableThreadLocal<String> threadLocal) {
        this.threadLocal = threadLocal;
    }

    @Override
    public void run() {
        try {
            String value = threadLocal.get();
            // 使用value
        } finally {
            threadLocal.remove(); // 子线程中移除值
        }
    }
}
ThreadLocal原理

ThreadLocal是Thread中的成员变量,内部维护了一个Map:ThreadLocalMap

ThreadLocal.ThreadLocalMap threadLocals =null;

用来保存线程私有变量

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {

            /** The value associated with this ThreadLocal. */

            Object value;

            Entry(ThreadLocal<?> k, Object v) {

                super(k);

                value = v;

            }

        }

}

ThreadLocalMap中Entry的key使用WeakReference指向ThreadLocal,value为线程私有变量。

ThreadLocalMap和Thread的生命周期相同,在JVM中每个线程拥有一个方法调用栈,ThreadLocalMap并不会随着方法栈帧的进栈出栈而创建回收,

需要调用ThreadLocal::remove方法对变量进行清理,避免内存泄露:

public void remove() {

    ThreadLocalMap m = getMap(Thread.currentThread());

    if(m !=null)

        m.remove(this);

}

private void remove(ThreadLocal<?> key) {

    Entry[] tab = table;

    intlen = tab.length;

    inti = key.threadLocalHashCode & (len-1);

    for(Entry e = tab[i];

         e !=null;

         e = tab[i = nextIndex(i, len)]) {

        if(e.get() == key) {

            e.clear();

            expungeStaleEntry(i);

            return;

        }

    }

}

WeakReference是为了兜底,如果没有显式的执行ThreadLocal::remove,那么在下一次GC时,ThreadLocal.Entry的key将被回收,设置为null,在下次调用ThreadLocal::get、ThreadLocal::set方法时,会进行null值的检查,如果为空,会进行value的回收,具体方法:

get:

set:

文件被锁原因分析

  1. 文件输入输出流(File InputStream/OutputStream)未关闭: 如果在Java程序中使用文件流进行读写操作,而在使用完毕后没有正确关闭流,那么即使文件在文件系统中被删除,Java程序中的文件流仍然持有对该文件的引用。这会导致操作系统无法回收文件所占用的空间。
FileInputStream fis =new FileInputStream("file.txt");// ... 进行文件操作 ...// 忘记关闭流
  1. 文件通道(FileChannel)未关闭: 类似地,如果使用java.nio包中的FileChannel进行文件操作,也必须确保操作完成后关闭通道。
FileChannel channel = FileChannel.open(Paths.get("file.txt"));// ... 进行文件操作 ...// 忘记关闭通道
  1. RandomAccessFile未关闭RandomAccessFile也用于文件的随机访问操作,同样需要在操作完成后关闭。本次不涉及
RandomAccessFile raf =new RandomAccessFile("file.txt","rw");// ... 进行文件操作 ...// 忘记关闭RandomAccessFile
  1. 文件锁(FileLock)未释放: 如果程序中使用了文件锁,那么即使文件被删除,持有锁的程序仍然可以访问文件内容。在删除文件前应该确保释放所有文件锁。
FileLock lock = channel.lock();// ... 进行文件操作 ...// 忘记释放锁
  1. 使用文件路径或文件对象引用: 即使文件被删除,只要程序中还保留有指向该文件的File对象或者文件路径的引用,就可能导致操作系统无法回收文件占用的空间。
File file =new File("file.txt");// ... 进行文件操作 ...// 忘记清理File对象引用

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

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

相关文章

学生故事|勇于创新,拒绝“一成不变”的设计

对于JIANG MANQI而言&#xff0c;室内设计一直是她钟爱的行业选择。 在没有进入莱佛士学习之前&#xff0c;MANQI受家人的影响&#xff0c;从小就对设计行业比较感兴趣。而她选择室内设计&#xff0c;是觉得室内设计是比较有前途的一个专业&#xff0c;随着人们生活品质的提高…

数字化转型-工具变量数据集

01、数据介绍 数字化转型是指企业或个人利用数字技术&#xff0c;如大数据、云计算、人工智能等&#xff0c;对其业务流程、运营模式、决策方式等进行全面、深入的变革&#xff0c;以提高效率、降低成本、提升质量、增强竞争力。在这个过程中&#xff0c;工具变量扮演着至关重…

SpringBoot 集成Nacos注册中心和配置中心-支持自动刷新配置

SpringBoot 集成Nacos注册中心和配置中心-支持自动刷新配置 本文介绍SpringBoot项目集成Nacos注册中心和配置中心的步骤&#xff0c;供各位参考使用 1、配置pom.xml 文件 在pom.xml文件中定义如下配置和引用依赖&#xff0c;如下所示&#xff1a; <properties><pr…

MATLAB求和函数

语法 S sum(A) S sum(A,“all”) S sum(A,dim) S sum(A,vecdim) S sum(,outtype) S sum(,nanflag) 说明 示例 S sum(A) 返回沿大小大于 1 的第一个数组维度计算的元素之和。 如果 A 是向量&#xff0c;则 sum(A) 返回元素之和。 如果 A 是矩阵&#xff0c;则 sum(A) 将…

如何在Linux CentOS部署宝塔面板并实现固定公网地址访问内网宝塔

文章目录 一、使用官网一键安装命令安装宝塔二、简单配置宝塔&#xff0c;内网穿透三、使用固定公网地址访问宝塔 宝塔面板作为建站运维工具&#xff0c;适合新手&#xff0c;简单好用。当我们在家里/公司搭建了宝塔&#xff0c;没有公网IP&#xff0c;但是想要在外也可以访问内…

Spring Boot 中如何处理存取 MySQL 中 JSON 类型的字段

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的全栈工程师 欢迎分享 / 收藏 / 赞 / 在看…

K8S部署Nginx与问题

【containerd错误解决系列】failed to create shim task, OCI runtime create failed, unable to retrieve OCI... 环境 # cat /etc/redhat-release CentOS Linux release 8.0.1905 (Core) # uname -r 4.18.0-348.rt7.130.el8.x86_64 问题及现象 1、pod的状态全部都是Conta…

谷粒商城实战(013 业务-认证服务-短信验证)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第211p-第p219的内容 介绍 认证中心要集成 社交登录、OAuth2.0、单点登录 等功能 OAuth 2.0&#xff1a; 问题解决&#xff1a; OAuth 2.0 主要…

鸿蒙应用开发之Web组件2

前面学习了加载Web组件,在使用这个组件之前需要设置网络加载的权限,否则是不能使用Web组件,所以大家在使用这个组件时,需要仔细检查是否有设置这个权限。 如果Web组件只是默认加载一次连接,就可以使用构造时传入的参数来决定,如果想不断地变换不同的网络地址,就需要使用…

「GO基础」在Windows上安装Go编译器并配置Golang开发环境

文章目录 1、安装Go语言编译程序1.1、下载GoLang编译器1.2、安装GoLang编译器 2、配置Golang IDE运行环境2.1、配置GO编译器2.1.1、GOROOT 概述2.1.2、GOROOT 作用2.1.2、配置 GOROOT 2.2、配置GO依赖管理2.2.1、Module管理依赖2.2.2、GOPATH 管理依赖 2.3、运行GO程序2.3.1、创…

05_数组和结构体

结构体 结构体的使用(重点) 结构体值传参 传值是指将参数的值拷贝一份传递给函数&#xff0c;函数内部对该参数的修改不会影响到原来的变量 结构体地址传递 传址是指将参数的地址传递给函数&#xff0c;函数内部可以通过该地址来访问原变量&#xff0c;并对其进行修改。…

大量excel文件私密性较强 需要密码保护 如何给excel文件批量加密

一&#xff0c;前言 在现代办公环境中&#xff0c;Excel文件已成为数据存储和交流的常见工具。然而&#xff0c;随着数据泄露和信息安全问题的日益严重&#xff0c;如何保护Excel文件的安全性成为了我们关注的焦点。批量加密Excel文件成为了一种有效的解决方案&#xff0c;它可…

【光伏行业】光伏发电的应用领域

光伏发电作为一种清洁、可再生的能源技术&#xff0c;在多个领域都有广泛的应用。以下是一些光伏发电的主要应用领域&#xff1a; 住宅和商业建筑&#xff1a;光伏板可以安装在屋顶上&#xff0c;为建筑提供电力。这不仅降低了对电网的依赖&#xff0c;还减少了电费支出&#x…

Spring (四) 之配置及配置文件的操作

文章目录 1、Spring 基于注解的配置基于注解的配置引入依赖包配置实体类数据访问层业务层业务层实现测试 2、Bean和Component和Configuration的区别1 Bean:2 Component:3 Configuration:总结&#xff1a; 区别Component和Configuration区别 3、Spring读取properties配置文件准备…

【C++】开始使用优先队列

送给大家一句话: 这世上本来就没有童话&#xff0c;微小的获得都需要付出莫大的努力。 – 简蔓 《巧克力色微凉青春》 开始使用优先队列 1 前言2 优先队列2.1 什么是优先队列2.2 使用手册2.3 仿函数 3 优先队列的实现3.1 基本框架3.2 插入操作3.3 删除操作3.4 其他函数 4 总结T…

【ZYNQ】PS和PL数据交互丨AXI总线(主机模块RTL代码实现)

文章目录 一、PS-PL数据交互桥梁&#xff1a;AXI总线1.1 AXI总线和AXI4总线协议1.2 PS-PL数据传输的主要场景1.2.1 PL通过AXI_HP操作DDR3 Controller读写DDR31.2.2 PS作主机使用GP接口传输数据 1.3 AXI端口带宽理论1.4 AXI 总线的读写分离机制1.5 握手机制1.6 AXI_Lite总线1.7 …

【软考】设计模式之命令模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 优缺点5.1 优点5.2 缺点 6. 适用性7.java示例 1. 说明 1.命令模式&#xff08;Command Pattern&#xff09;是一种数据驱动的设计模式。2.属于行为型模式。3.请求以命令的形式被封装在对象中&#xff0c;并传递给调用对象。4.调用对…

制作直通网线和交叉网线

制作直通网线和交叉网线 1. 网络直通线2. 网络交叉线References 双绞线的连接方法有两种&#xff1a;直通连接和交叉连接 。 直通连接是将双绞线的两端分别都依次按白橙、橙、白绿、蓝、白蓝、绿、白棕、棕色的顺序 (国际 EIA/TIA 568B 标准) 压入 RJ45 水晶头内。这种方法制作…

剧本杀小程序:线上剧本杀成为行业必然趋势

剧本杀作为一个社交娱乐游戏方式&#xff0c;受到了年轻人的喜爱。剧本杀是一个新型的游戏方式&#xff0c;能够带大众带来新鲜感和刺激感&#xff0c;让玩家通过角色扮演进行游戏体验&#xff1b;并且剧本杀还具有较强的社交性&#xff0c;在当下快节奏生活下&#xff0c;以游…

【AI】在Windows10下部署本地LLM RAG服务

【背景】 上一篇介绍了如何用Ubuntu命令行部署ollama LLM+RAG服务。部署后等于拥有了基于内网的AI Saas服务,其它内网用户可以通过默认的网址访问Playground对AI进行问答。 【概念】 RAG:通过词向量技术,将文件内容向量化后,通过语言模型以自然交流的形式得到文本相关的…