《Java 对象池技术:性能优化的利器》

一、引言
在 Java 开发中,对象池技术作为一种优化手段,有着重要的地位。对象的生命周期通常包括创建、使用和清除三个阶段。在这个过程中,对象的创建和清除会带来一定的开销,而对象池技术则可以有效地减少这些开销,提高程序的性能和资源利用率。
对象池技术的优点主要体现在以下几个方面:
复用池中对象,消除了创建对象、回收对象所产生的内存开销、CPU 开销以及(若跨网络)产生的网络开销。对于比较耗时的构造函数和终结方法来说非常合适,不必重复初始化对象状态。
在一些特定场景下,如受限的、不需要可伸缩性的环境(比如移动设备),CPU 性能不够强劲,内存比较紧张,垃圾收集和内存抖动会造成比较大的影响,此时对象池技术可以提高内存管理效率,响应性比吞吐量更为重要。
对于数量受限的资源,比如数据库连接,对象池可以预创建并存储多个连接,供需要时直接使用,显著提高性能。
对于创建成本高昂的对象,如线程池、字节数组池等,可以斟酌是否池化。如果有成熟的库方案,建议使用,比如 JDK 自带的 ThreadPoolExecutor。
然而,对象池技术也存在一些缺点:
现在 Java 的对象分配操作不比 C 语言的 malloc 调用慢,对于轻中量级的对象,分配 / 释放对象的开销可以忽略不计。
并发环境中,多个线程可能(同时)需要获取池中对象,进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞,这种开销要比创建销毁对象的开销高数百倍。
由于池中对象的数量有限,势必成为一个可伸缩性瓶颈。
很难正确的设定对象池的大小,如果太小则起不到作用,如果过大,则占用内存资源高。可以起一个线程定期扫描分析,将池压缩到一个合适的尺寸以节约内存,但为了获得不错的分析结果,在扫描期间可能需要暂停复用以避免干扰(造成效率低下),或者使用非常复杂的算法策略(增加维护难度)。
设计和使用对象池容易出错,设计上需要注意状态同步,这是个难点,使用上可能存在忘记归还(就像 C 语言编程忘记 free 一样),重复归还(可能需要做个循环判断一下是否池中存在此对象,这也是个开销),归还后仍旧使用对象(可能造成多个线程并发使用一个对象的情况)等问题。
对象池有其特定的适用场景:
受限的,不需要可伸缩性的环境(比如移动设备):CPU 性能不够强劲,内存比较紧张,垃圾收集,内存抖动会造成比较大的影响,需要提高内存管理效率,响应性比吞吐量更为重要。
数量受限的资源,比如数据库连接;(自己写比较容易埋坑,建议使用成熟的库方案,比如 c3p0)。
创建成本高昂的对象,可斟酌是否池化,比较常见的有线程池,字节数组池等;(如果有,则建议使用成熟的库方案,比如 JDK 自带的 ThreadPoolExecutor,而不是自己写)。
例如,在数据库连接池中,建立数据库连接是时间消耗的操作。对象池预创建并存储多个连接,供需要时直接使用,显著提高性能。在 Java 中,常见的对象池应用场景还包括线程池和缓冲池。线程池提供了一个已初始化的线程集合,能够快速响应并处理任务。缓冲池如 ByteBuffer 中的直接内存缓冲,它的创建和销毁成本高,对象池可以优化其性能。
总之,对象池技术在 Java 开发中具有重要的作用,但在使用时需要根据具体情况进行权衡,选择合适的应用场景,并注意对象的状态管理和资源泄漏的问题。当正确使用时,对象池可以显著提高应用的性能和可靠性。
二、Java 对象池技术的优点

Java 对象池技术具有以下优点:

  1. 复用池中对象,减少内存和 CPU 开销,减轻垃圾收集器负担,避免内存抖动:复用池中对象消除了创建对象、回收对象所产生的内存开销、CPU 开销以及(若跨网络)产生的网络开销。对于比较耗时的构造函数和终结方法来说非常合适,不必重复初始化对象状态。例如,在一些频繁创建和销毁对象的场景中,如数据库连接池、线程池等,对象池可以显著减少这些开销。
  2. 适用于受限环境、数量受限资源和创建成本高昂的对象:
    受限环境:在受限的、不需要可伸缩性的环境(比如移动设备)中,CPU 性能不够强劲,内存比较紧张,垃圾收集和内存抖动会造成比较大的影响。此时,对象池技术可以提高内存管理效率,响应性比吞吐量更为重要。
    数量受限资源:对于数量受限的资源,比如数据库连接,对象池可以预创建并存储多个连接,供需要时直接使用,显著提高性能。自己编写数据库连接池容易埋坑,建议使用成熟的库方案,比如 c3p0。
    创建成本高昂的对象:对于创建成本高昂的对象,如线程池、字节数组池等,可以斟酌是否池化。如果有成熟的库方案,建议使用,比如 JDK 自带的 ThreadPoolExecutor。
    对象池技术在特定场景下能够显著提高程序的性能和资源利用率,但也需要注意其缺点,并在使用时根据具体情况进行权衡,选择合适的应用场景。
    三、Java 对象池技术的实现方式

关键步骤
初始化:创建一定数量的对象并存入池中。在 Java 对象池技术中,初始化阶段是至关重要的一步。例如,可以创建一个对象池管理类,在类的构造函数中或者特定的初始化方法中,预先创建一定数量的对象并存储在一个数据结构中,如栈(Stack)或列表(List)。这样,当后续需要使用对象时,可以直接从池中获取,避免了频繁创建对象的开销。
借出:当需要对象时,从池中借出一个对象,并标记为正在使用。当程序需要使用对象时,通过对象池管理类的特定方法,如 borrowObj (),从池中获取一个对象。在这个过程中,对象池需要标记该对象为正在使用状态,以便后续进行管理。例如,可以使用一个集合来存储已借出对象的标识,如对象的哈希码(hashCode)。
归还:使用完毕后,将对象归还到池中,以供再次借出。当对象使用完毕后,程序应该及时将对象归还给对象池。通过调用对象池管理类的 returnObj () 方法,将对象重新放入池中,并从已借出对象的集合中移除该对象的标识。这样,对象就可以再次被借出使用。
销毁:在某些场景下,需要销毁池中对象,如释放连接。在特定情况下,如程序结束或者需要释放资源时,可能需要销毁对象池中的对象。可以通过对象池管理类的 destory () 方法来实现。在销毁对象之前,需要确保所有借出的对象都已经归还,否则不能进行销毁操作。销毁对象时,可以遍历对象池中的对象,逐个进行清理操作,如关闭连接、释放资源等。
示例代码
通过手动实现一个简陋的对象池管理类,展示对象池的基本操作,包括增加对象、借出对象、归还对象和销毁池中对象。以下是一个简单的对象池管理类的示例代码:
import java.io.Closeable;
import java.io.IOException;
import java.util.HashSet;
import java.util.Stack;

public class MyObjectPool {
// 池子大小
private Integer size = 5;
// 对象池栈。后进先出
private Stack stackPool = new Stack<>();
// 借出的对象的 hashCode 集合
private HashSet borrowHashCodeSet = new HashSet<>();

/**
 * 增加一个对象
 *
 * @param t
 */
public synchronized void addObj(T t) {
    if ((stackPool.size() + borrowHashCodeSet.size()) == size) {
        throw new RuntimeException("池中对象已经达到最大值");
    }
    stackPool.add(t);
    System.out.println("添加了对象:" + t.hashCode());
}

/**
 * 借出一个对象
 *
 * @return
 */
public synchronized T borrowObj() {
    if (stackPool.isEmpty()) {
        System.out.println("没有可以被借出的对象");
        return null;
    }
    T pop = stackPool.pop();
    borrowHashCodeSet.add(pop.hashCode());
    System.out.println("借出了对象:" + pop.hashCode());
    return pop;
}

/**
 * 归还一个对象
 *
 * @param t
 */
public synchronized void returnObj(T t) {
    if (borrowHashCodeSet.contains(t.hashCode())) {
        stackPool.add(t);
        borrowHashCodeSet.remove(t.hashCode());
        System.out.println("归还了对象:" + t.hashCode());
        return;
    }
    throw new RuntimeException("只能归还从池中借出的对象");
}

/**
 * 销毁池中对象
 */
public synchronized void destory() {
    if (!borrowHashCodeSet.isEmpty()) {
        throw new RuntimeException("尚有未归还的对象,不能关闭所有对象");
    }
    while (!stackPool.isEmpty()) {
        T pop = stackPool.pop();
        try {
            pop.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    System.out.println("已经销毁了所有对象");
}

}

以池化 Redis 连接对象 Jedis 为例,演示如何使用对象池。首先,假设已经有了一个 Redis 服务,并且引入了 Java 中连接 Redis 需要用到的 Maven 依赖:

redis.clients
jedis
4.2.0

正常情况下 Jedis 对象的使用方式如下:
Jedis jedis = new Jedis(“localhost”, 6379);
String name = jedis.get(“name”);
jedis.close();

如果使用上面的对象池,就可以像下面这样使用:
MyObjectPool pool = new MyObjectPool<>();
Jedis jedis = pool.borrowObj();
String name = jedis.get(“name”);
pool.returnObj(jedis);

四、Java 对象池技术的应用场景

数据库连接池
建立数据库连接是时间消耗的操作,对象池预创建并存储多个连接,供需要时直接使用,显著提高性能。在 Java 应用程序中,有效地管理数据库连接池是提升性能和资源利用率的关键。常见的数据库连接池实现有 Apache Commons DBCP、HikariCP 和 Alibaba Druid 等。以 HikariCP 为例,首先需要引入依赖,然后进行配置和初始化,设置连接池参数如最大连接数、连接超时和闲置超时等,以优化数据库连接池的性能和资源利用。同时,要注意在使用完毕数据库连接后,及时释放连接,避免连接泄露导致连接池资源耗尽和性能下降。还可以使用连接池的监控和诊断工具,实时查看连接池的使用情况和性能指标,帮助及时发现和解决连接池性能瓶颈。
线程池
线程的创建和销毁开销大,线程池提供了一个已初始化的线程集合,能够快速响应并处理任务。开发过程中,合理地使用线程池能够带来降低资源消耗、提高线程速度和提高线程可管理性等好处。Java 中通过 Executors 提供了多种线程池,如 newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool 和 newSingleThreadExecutor,分别适用于不同的应用场景。在使用线程池时,要了解其原理,提交一个任务到线程池中,线程池会判断线程池里的核心线程是否都在执行任务,如果不是则创建一个新的工作线程来执行任务。
缓冲池
如 ByteBuffer 中的直接内存缓冲,它的创建和销毁成本高,对象池可以优化其性能。缓冲流自带缓冲区,可以提高原始字节流、字符流读写数据的性能。以字符缓冲输入流和字符缓冲输出流为例,它们有 8KB 缓冲池,提高了字符输出流写数据的性能,除此以外多了换行功能。对于直接缓冲区,虽然分配它们很昂贵,但是对于大型和长寿命的缓冲区使用直接缓冲区,或者创建一个大型缓冲区,按需分割部分,并在不再需要它们时将它们返回以重新使用,效率更高。同时,可以使用工具如 VisualVM(带有 BufferMonitor 插件)和 FusionReactor 轻松监视缓冲池的使用情况。
五、注意事项

对象复位
从对象池返回的对象需要确保是 “干净” 的,即所有状态都被正确复位,以防止数据混淆或安全问题。当从对象池中获取一个对象进行使用后,在归还对象之前,必须将对象的状态重置为初始状态。例如,如果是数据库连接对象,需要确保连接的状态被重置,关闭可能打开的事务等。这样当下次从对象池中获取该对象时,它不会保留上次使用的状态,避免了数据的混乱和潜在的安全风险。
资源泄漏
对象如果未被正确返回到池中,可能会导致资源泄漏。在使用对象池的过程中,务必确保在对象使用完毕后及时将其归还给对象池。如果对象没有被正确归还,随着时间的推移,对象池中的可用对象会逐渐减少,最终可能导致系统无法获取所需的资源。例如,在使用数据库连接池时,如果一个连接在使用后没有被归还,当其他部分的代码需要数据库连接时,可能会因为无法从连接池中获取连接而导致操作失败。
池的大小管理
池太大会浪费资源,池太小可能无法满足需求。在设置对象池的大小时,需要根据实际的应用场景进行合理的评估。如果池的大小设置得过大,会占用过多的内存资源,可能会影响系统的整体性能。例如,在一个小型的应用程序中,如果设置了一个非常大的数据库连接池,可能会导致大量的数据库连接处于闲置状态,浪费了系统资源。另一方面,如果池的大小设置得太小,可能无法满足高并发情况下的需求。例如,在一个高并发的 Web 应用中,如果线程池的大小设置得太小,可能会导致任务排队等待,影响系统的响应时间。
六、总结

Java 对象池技术是一种强大的工具,特别是在高并发、资源敏感的场景下。正确使用对象池可以显著提高应用的性能和可靠性,但需要注意对象的状态管理和资源泄漏的问题,以及合理管理池的大小。
总结起来,Java 对象池技术具有以下特点:
一、优点显著
复用池中对象,减少内存和 CPU 开销,减轻垃圾收集器负担,避免内存抖动,对于比较耗时的构造函数和终结方法来说非常合适,不必重复初始化对象状态。
适用于受限环境、数量受限资源和创建成本高昂的对象,如在受限的移动设备环境中提高内存管理效率,对于数据库连接和创建成本高昂的线程池、字节数组池等可显著提高性能。
二、实现方式多样
通过关键步骤实现对象池,包括初始化时创建一定数量的对象并存入池中,借出时从池中获取对象并标记为正在使用,归还时将对象放回池中,销毁时确保所有借出对象已归还并清理池中对象。
可以通过手动实现简陋的对象池管理类展示基本操作,也可以以池化 Redis 连接对象 Jedis 为例演示对象池的实际应用。
三、应用场景广泛
数据库连接池预创建并存储多个连接,供需要时直接使用,显著提高性能,常见的实现有 Apache Commons DBCP、HikariCP 和 Alibaba Druid 等,使用时要注意配置和初始化参数,及时释放连接,避免连接泄露。
线程池提供已初始化的线程集合,能够快速响应并处理任务,Java 中通过 Executors 提供多种线程池,使用时要了解其原理,合理提交任务。
缓冲池如 ByteBuffer 中的直接内存缓冲创建和销毁成本高,对象池可以优化其性能,缓冲流自带缓冲区可提高读写性能,对于直接缓冲区可按需分割和复用。
四、注意事项需牢记
对象复位:从对象池返回的对象需要确保是 “干净” 的,即所有状态都被正确复位,以防止数据混淆或安全问题。
资源泄漏:对象如果未被正确返回到池中,可能会导致资源泄漏,在使用对象池过程中务必及时归还对象。
池的大小管理:池太大会浪费资源,池太小可能无法满足需求,需要根据实际应用场景合理评估和设置池的大小。
总之,Java 对象池技术在特定场景下能够发挥重要作用,但在使用时需要综合考虑各种因素,以实现最佳的性能和可靠性。

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

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

相关文章

卷积神经网络学习记录

目录 神经网络基础定义&#xff1a; 基本组成部分 工作流程 卷积层&#xff08;卷积定义&#xff09;【CONV】&#xff1a; 卷积层&#xff08;Convolutional Layer&#xff09; 特征提取&#xff1a;卷积层的主要作用是通过卷积核&#xff08;或滤波器&#xff09;运算提…

数据结构初阶---复杂度

一、数据结构前言 1.数据结构与算法 数据结构(Data Structure)&#xff1a;是计算机组织、存储数据的一种方式&#xff0c;指相互之间存在一种或多种特定关系的数据元素的集合。 算法(Algorithm)&#xff1a;就是定义良好的计算过程&#xff0c;他取一个或一组的值为输入&am…

二叉树的层次遍历

二叉树的层次遍历 题目 https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ 描述 给你一个二叉树,请你返回其按 层次遍历 得到的节点值(即逐层地,从做到右访问所有节点) 代码实现 通过两个数组来交替打印 class Solution(object):def levelOrder

网络安全中的数据科学如何重新定义安全实践?

组织每天处理大量数据&#xff0c;这些数据由各个团队和部门管理。这使得全面了解潜在威胁变得非常困难&#xff0c;常常导致疏忽。以前&#xff0c;公司依靠 FUD 方法&#xff08;恐惧、不确定性和怀疑&#xff09;来识别潜在攻击。然而&#xff0c;将数据科学集成到网络安全中…

【Linux系统】—— 基本指令(四)

【Linux系统】—— 基本指令&#xff08;三&#xff09; 1「find」指令2 「grep」指令2.1 初识「grep」指令2.2 「grep」指令 选项 3 打包压缩基本知识4 「zip / unzip」指令5「tar」命令6 文件互传6.1 Linux 与 Windows 互传6.1.1 Linux向Windows传输6.1.2 Windows向Linux传输…

将django+vue项目发布部署到服务器

1.部署django后端服务 部署架构 1.1 下载依赖插件 pip3.8 freeze > requirements.txt1.2 安装依赖插件 pip3 install -r requirements.txt1.3 安装mysql数据库 apt install mysql-server初始化数据库 CREATE USER admin% IDENTIFIED WITH mysql_native_password BY 123…

网络层协议IP

对于网络层我们直接通过IP协议来了解其内容 一.IP协议 首先我们先来了解几个概念&#xff1a; 主机&#xff1a;配有IP地址&#xff0c;但是不进行路由控制的设备 路由器&#xff1a;配有IP地址&#xff0c;同时进行路由控制的设备 节点&#xff1a;主机和路由器的统称 所以现在…

AIGC-----AIGC在虚拟现实中的应用前景

AIGC在虚拟现实中的应用前景 引言 随着人工智能生成内容&#xff08;AIGC&#xff09;的快速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术的应用也迎来了新的契机。AIGC与VR的结合为创造沉浸式体验带来了全新的可能性&#xff0c;这种组合不仅极大地降低了VR内容的…

如何利用 Puppeteer 的 Evaluate 函数操作网页数据

介绍 在现代的爬虫技术中&#xff0c;Puppeteer 因其强大的功能和灵活性而备受青睐。Puppeteer 是一个用于控制 Chromium 或 Chrome 浏览器的 Node.js 库&#xff0c;提供了丰富的 API 接口&#xff0c;能够帮助开发者高效地处理动态网页数据。本文将重点讲解 Puppeteer 的 ev…

【运维】 使用 shell 脚本实现类似 jumpserver 效果实现远程登录linux 服务器

实现效果 通过序号选择登录&#xff1a; 配置证书登录 配置证书登录可以免去每次都输入密码的麻烦。详见另一篇博文&#xff1a; 【ssh】使用秘钥对&#xff08;公钥/私钥&#xff09;登录linux主机以及原理介绍 自动登录脚本 直接复用以下脚本即可&#xff0c;在 server…

sqlmap学习,打靶sqli-labs.(1-19)

前言&#xff1a;用于学习sqlmap的简单使用&#xff0c;使用sqli-labs靶场进行测试。 当然,在实战中,考虑的更多&#xff0c;例如如何隐藏自己(特征码),编码加解密、sqlmap抓包调试分析等... 不过那些都是后话&#xff0c;太遥远...基础NO.1&#xff01;&#xff01; 先贴上我…

A045-基于spring boot的个人博客系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

[RabbitMQ] 保证消息可靠性的三大机制------消息确认,持久化,发送方确认

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

Unity中动态生成贴图并保存成png图片实现

实现原理&#xff1a; 要生成长x宽y的贴图&#xff0c;就是生成x*y个像素填充到贴图中&#xff0c;如下图&#xff1a; 如果要改变局部颜色&#xff0c;就是从x1到x2(x1<x2),y1到y2(y1<y2)这个范围做处理&#xff0c; 或者要想做圆形就是计算距某个点&#xff08;x1,y1&…

sklearn学习

介绍&#xff1a;scaler&#xff1a;换算的意思 1. 归一化MinMaxScaler() 归一化的意思是将一堆数&#xff0c;如果比较离散&#xff0c;为了让数据更适合模型训练&#xff0c;将离散的数据压缩到0到1之间&#xff0c;以方便模型更高效优质的学习&#xff0c;而对数据的预处理…

windows下安装wsl的ubuntu,同时配置深度学习环境

写在前面&#xff0c;本次文章只是个人学习记录&#xff0c;不具备教程的作用。个别信息是网上的&#xff0c;我会标注&#xff0c;个人是gpt生成的 安装wsl 直接看这个就行&#xff1b;可以不用备份软件源。 https://blog.csdn.net/weixin_44301630/article/details/1223900…

Flutter:启动屏逻辑处理02:启动页

启动屏启动之后&#xff0c;制作一个启动页面 新建splash&#xff1a;view 视图中只有一张图片sliding.png就是我们的启动图 import package:flutter/material.dart; import package:get/get.dart; import index.dart; class SplashPage extends GetView<SplashController…

【AIGC】如何准确引导ChatGPT,实现精细化GPTs指令生成

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | 提示词Prompt应用实例 文章目录 &#x1f4af;前言&#x1f4af;准确引导ChatGPT创建爆款小红书文案GPTs指令案例&#x1f4af; 高效开发GPTs应用的核心原则明确应用场景和目标受众构建多样化风格模板提问与引…

【通俗理解】隐变量的变分分布探索——从公式到应用

【通俗理解】隐变量的变分分布探索——从公式到应用 关键词提炼 #隐变量 #变分分布 #概率模型 #公式推导 #期望最大化 #机器学习 #变分贝叶斯 #隐马尔可夫模型 第一节&#xff1a;隐变量的变分分布的类比与核心概念【尽可能通俗】 隐变量的变分分布就像是一场“捉迷藏”游戏…

云计算-华为HCIA-学习笔记

笔者今年7月底考取了华为云计算方向的HCIE认证&#xff0c;回顾从IA到IE的学习和项目实战&#xff0c;想整合和分享自己的学习历程&#xff0c;欢迎志同道合的朋友们一起讨论&#xff01; 第三章&#xff1a;常见设备 交换机 二层交换机和三层交换机&#xff0c;所谓二层交换机…