当前位置: 首页 > news >正文

GenericObjectPool对象池化的介绍与用法

前言

GenericObjectPool 是 Apache Commons Pool 库的一部分,它提供了一个通用的对象池实现,允许用户在需要时从池中借用和返回对象,而不是每次需要一个新实例时都创建一个。这种方法可以显著提高性能特别是对于创建开销大或需要频繁使用的对象

哪些对象创建、销毁成本较高

创建和销毁成本较大的 对象有哪些、原因是啥:

  1. 数据库连接:建立数据库连接涉及网络通讯以及数据库服务器上的资源分配和初始化。这些过程可能需要消耗显著的时间和系统资源。因此,数据库连接池(如 HikariCP、c3p0)通常用于管理这些连接,以减少每次操作所需的创建和销毁开销。

  2. 线程:创建和销毁线程涉及操作系统级的资源分配,这可能是一个高代价的操作,尤其是在需要频繁创建和销毁大量线程的场景中。Java 的 ExecutorService 提供了线程池功能,可以有效地复用线程来处理多个任务。

  3. 网络连接:建立网络连接(如 TCP 连接)涉及网络协议的握手过程,这可能导致显著的延迟和资源消耗。复用网络连接(例如通过 HTTP 连接池)可以减少这种开销。

  4. 大对象或复杂对象:创建一些内存占用大构造过程复杂的对象也可能涉及较高的开销。例如,需要加载大量数据或执行复杂初始化逻辑的对象。

  5. 外部资源(如文件或其他系统资源):操作文件或与外部系统交互(通过 API 调用等)可能涉及显著的资源开销。缓存操作结果重用外部连接可以帮助降低这种开销。

为了管理资源创建和销毁的成本,通常采用以下策略

  1. 对象池化(Pooling):如数据库连接池、线程池和 HTTP 客户端连接池,通过重用现有对象来减少创建和销毁的频率。
  2. 缓存(Caching):缓存是另一种减少资源获取成本的策略,特别是当资源获取涉及昂贵的计算或外部系统调用时。
  3. 延迟初始化(Lazy Initialization):延迟(或按需)创建资源,直到首次使用时,可以推迟资源分配的开销,并可能省略对某些永远不会使用的资源的开销。

对象创建的步骤

在Java中,创建对象时的内存分配过程主要发生在堆(Heap)内存区域。整个过程受Java虚拟机(JVM)的垃圾收集(GC)算法和内存模型的管理。这个过程可以分为以下几个步骤:

  1. 类加载检查
    当代码尝试创建一个对象实例时(如通过new XXXClassName()),JVM首先检查这个类是否已被加载、链接和初始化。如果没有,系统会执行这些步骤。

  2. 为对象分配内存
    一旦类检查完成,JVM会在堆内存中为新对象分配内存。分配的内存大小根据对象的类定义来确定,假如类有String name、Integer age、Boolean isGirl,包括所有实例变量的大小及其他一些开销(如对象头信息)。这部分内存用于存储对象的状态(即字段或属性的值)。

    内存分配方式取决于垃圾收集器的具体实现。常见的分配方式包括:
    a. 指针碰撞(Bump the Pointer):如果堆内存是整齐划分的(使用的垃圾收集算法如Minor GC后),那么对象的分配仅仅是将指向堆中的空闲区域的指针向上移动对象大小的距离
    b. 空闲列表(Free List):如果堆内存是分散的(即存在被使用和未被使用的内存区域),JVM必须通过遍历一个列表来找到足够大的空间来存放新对象。

  3. 初始化内存空间
    内存分配完毕后,JVM将分配的内存空间初始化为零值(不包括对象头),确保对象的实例变量不会有Java默认值以外的任何值对象头一般包含对象的哈希码GC代年龄等信息。

  4. 设置对象头
    JVM设置对象头信息,包括这个对象是哪个类的实例对象的哈希码和锁信息等。

  5. 执行构造方法
    最后,JVM调用对象的构造函数。构造函数方法可能会对对象变量进行初始化,这可能会覆盖在第3步中设置的零值

这一过程尽管看起来复杂,但在现代虚拟机中非常高效。JVM的设计包括了多种优化,如线程本地内存分配(TLAB),这允许每个线程在自己的内存缓冲区中分配对象,从而减少线程间的竞争

为什么使用 GenericObjectPool?

当创建一个对象的成本(时间、资源等)很高时,就可以使用对象池。对象池通过重用现有的对象而不是每次需要时都创建新对象来提高性能和资源利用率。GenericObjectPool 提供了管理这种对象池的功能,包括创建、借用、返回以及销毁对象的机制

GenericObjectPool 的关键特性:

  1. 线程安全:GenericObjectPool 是线程安全的,可以在多线程环境中共享使用。
  2. 可配置性:提供了许多配置项,如最大对象数、最大空闲对象数、最小空闲对象数、当池中对象耗尽时的行为等。
  3. 泛型支持:它是泛型的,可以指定池中管理的对象类型。

原理和工作流程:

  1. 对象创建:
    GenericObjectPool 使用一个工厂模式来创建对象。它依赖于实现了 PooledObjectFactory 接口的工厂类来创建、初始化、激活、钝化和销毁对象。
  2. 借用对象 (borrowObject)
    当调用 borrowObject() 方法时,池尝试为请求提供一个实例。首先,它会检查池中是否有可用的空闲对象(已创建但未被借用的对象)。
  • 如果有空闲对象,池将返回一个,并将其标记为“已借用”
  • 如果没有空闲对象可用,但当前池中对象的总数未达到配置的最大数量(maxTotal),池将使用工厂创建一个新对象
  • 如果池已满(即,当前借用的对象数加上空闲对象数达到了 maxTotal),则 borrowObject() 方法的行为取决于配置的阻塞策略。它可以抛出异常阻塞等待直到有对象可用或者返回 null,具体取决于配置。
  1. 返回对象 (returnObject)
    使用完对象后,客户端应该通过调用 returnObject() 方法来归还对象到池中。
    归还操作通常涉及将对象标记为“空闲”,可能还会涉及钝化(重置或清理)对象以供将来重用。
  2. 对象销毁destroyObject()
    池可以根据配置策略(如空闲时间超过一定限制空闲对象超过一定数量等)决定销毁一些对象。
    销毁操作由工厂的 destroyObject() 方法完成。
  3. 维护活动
    GenericObjectPool 可以配置为执行后台维护线程,这个线程定期检查池中的对象根据配置执行对象的逐出(比如基于空闲时间或其他策略来逐出对象),确保池中保持活跃和清理的对象

关键配置参数:

  1. maxTotal:池中允许的最大对象数
  2. maxIdle:池中允许的最大空闲对象数
  3. minIdle:池中确保的最小空闲对象数
  4. maxWaitMillis:当池中没有可用对象时,borrowObject() 方法等待对象变为可用的最大时间(以毫秒为单位)。超时则抛出异常。
  5. testOnBorrow、testOnReturn、testWhileIdle:是否在借用、归还、空闲时检验对象

快速开始

在pom.xml 文件中,添加如下依赖:

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.11.1</version>
</dependency>

实现 池化对象工厂接口 PooledObjectFactory

import com.tmall.bombonera.client.model.Context;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;public class ContextPoolFactory implements PooledObjectFactory<Context> {@Overridepublic void activateObject(PooledObject<Context> pooledObject) throws Exception {// 激活对象:当这个对象下次从池中借用时,将调用 activateObject() 方法使其重新“激活”。激活通常包括将对象恢复到适合再次使用的状态pooledObject.getObject().init();}@Overridepublic void destroyObject(PooledObject<Context> pooledObject) throws Exception {}@Overridepublic PooledObject<Context> makeObject() throws Exception {// 创建新对象Context context = new Context();context.init();return new DefaultPooledObject<>(context);}@Overridepublic void passivateObject(PooledObject<Context> pooledObject) throws Exception {// 钝化对象:重置对象的状态,使其回到一个干净的初始状态,包括决定是否将对象的属性设置为 null 或重置为默认值}@Overridepublic boolean validateObject(PooledObject<Context> pooledObject) {// 验证对象是否可用:首先检查要归还的对象是否仍然有效。如果对象在使用过程中变得不再有效(例如,数据库连接关闭),那么该对象将不会被归还到池中,而是被销毁return true;}
}

定义上下文context的池子


import java.time.Duration;import javax.annotation.PostConstruct;import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class ContextPool {public GenericObjectPool<Context> genericObjectPool;/*** 初始化 genericObjectPool,设置工厂类、池化配置*/@PostConstructpublic void init() {ContextPoolFactory factory = new ContextPoolFactory();//设置对象池的相关参数GenericObjectPoolConfig poolConfig = initConfig();//新建一个对象池,传入对象工厂和配置genericObjectPool = new GenericObjectPool<>(factory, poolConfig);// 设置抛弃策略setAbandonedConfig(genericObjectPool);}/*** 获取一个对象** @return Context 上下文对象* @throws Exception 当获取对象失败时抛出*/public Context getContext() throws Exception {return genericObjectPool.borrowObject();}/*** 归还一个对象** @param context 上下文对象*/public void returnContext(Context context) {genericObjectPool.returnObject(context);}/*** 每5分钟打印对象池状态*/@Scheduled(fixedRate = 300000)public void printPoolStats() {UnionLogUtils.bizInfo("ContextPool stats: Created: {}, Borrowed: {}, Returned: {}, Destroyed: {}, Active: {}, Idle: {}",genericObjectPool.getCreatedCount(), genericObjectPool.getBorrowedCount(),genericObjectPool.getReturnedCount(), genericObjectPool.getDestroyedCount(),genericObjectPool.getNumActive(), genericObjectPool.getNumIdle());}/*** 池子初始化*/private GenericObjectPoolConfig initConfig() {GenericObjectPoolConfig cfg = new GenericObjectPoolConfig();cfg.setJmxNamePrefix("objectPool");//  对象总数cfg.setMaxTotal(5000);// 最大空闲对象数cfg.setMaxIdle(4000);// 最小空闲对象数cfg.setMinIdle(500);// 借对象阻塞最大等待时间// 获取资源的等待时间。blockWhenExhausted 为 true 时有效。-1 代表无时间限制,一直阻塞直到有可用的资源cfg.setMaxWait(Duration.ofMillis(10));// 最小驱逐空闲时间cfg.setMinEvictableIdleTime(Duration.ofMillis(5000));// 每次驱逐数量  资源回收线程执行一次回收操作,回收资源的数量。cfg.setNumTestsPerEvictionRun(10);// 回收资源线程的执行周期,默认 -1 表示不启用回收资源线程cfg.setTimeBetweenEvictionRuns(Duration.ofMillis(5000));// 资源耗尽时,是否阻塞等待获取资源,默认 truecfg.setBlockWhenExhausted(false);return cfg;}/*** 设置抛弃策略** @param genericObjectPool 对象池*/private void setAbandonedConfig(GenericObjectPool<Context> genericObjectPool) {// 设置抛弃策略,当对象借出较长时间未归还则进行抛弃,认为对象泄漏AbandonedConfig abandonedConfig = new AbandonedConfig();// 在Maintenance的时候检查是否有泄漏abandonedConfig.setRemoveAbandonedOnMaintenance(true);// 在borrow的时候检查是否有泄漏abandonedConfig.setRemoveAbandonedOnBorrow(true);//如果一个对象borrow之后1秒还没有返还给pool,认为是泄漏的对象abandonedConfig.setRemoveAbandonedTimeout(1);genericObjectPool.setAbandonedConfig(abandonedConfig);}
}

调用案例

@Resourceprivate ContextPool contextPool;/*** 调用底层引擎获取判定结果, 包括参数的构造** @param rtaRequest tanx请求参数* @return 巨浪rta引擎返回的结果*/private AskResultV4 getAskResult(RtaRequest rtaRequest) {Context context = null;try {// 借用对象context = contextPool.getContext();context.setChannelId(tanxRtaConfig.getChannel());context.setAdvertisingSpaceId(tanxRtaConfig.getAdvertisingSpaceId());context.setBidSwtich(tanxRtaConfig.getNeedBid());...} catch (Exception e) {} finally {if (Objects.nonNull(context)) {// 归还对象contextPool.returnContext(context);}}}

相关文章:

  • Linux在创建用户的时候遇到的错误:useradd: Permission denied.useradd:无法锁定 /etc/passwd,请稍后再试。
  • LeetCode | 434.字符串中的单词数
  • 数据结构之链表的经典笔试题
  • C++笔试强训day42
  • 【实例分享】访问后端服务超时,银河麒麟服务器操作系统分析及处理建议
  • 有趣的傅里叶变换与小波变换对比(Python)
  • Redis系列-4 Redis集群介绍
  • Linux基础指令(二)(文件、权限等)
  • CAN测试工具——BUSMASTER
  • 访问api是如何使用的
  • 学会python——读取大文本文件(python实例六)
  • 【系统架构】REST风格
  • VMware Ubuntu 虚拟机网卡消失及解决办法
  • C语言期末考试大纲详解
  • MySql 各种 join
  • [NodeJS] 关于Buffer
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • 11111111
  • android图片蒙层
  • es6
  • JavaScript设计模式与开发实践系列之策略模式
  • React 快速上手 - 07 前端路由 react-router
  • 电商搜索引擎的架构设计和性能优化
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 三栏布局总结
  • 深度学习中的信息论知识详解
  • 推荐一个React的管理后台框架
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • AI算硅基生命吗,为什么?
  • 翻译 | The Principles of OOD 面向对象设计原则
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ###项目技术发展史
  • #stm32驱动外设模块总结w5500模块
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • %@ page import=%的用法
  • (23)Linux的软硬连接
  • (55)MOS管专题--->(10)MOS管的封装
  • (vue)el-tabs选中最后一项后更新数据后无法展开
  • (二)构建dubbo分布式平台-平台功能导图
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (蓝桥杯每日一题)love
  • (十八)SpringBoot之发送QQ邮件
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (五)关系数据库标准语言SQL
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • (转)AS3正则:元子符,元序列,标志,数量表达符
  • .gitignore不生效的解决方案
  • .Net Memory Profiler的使用举例
  • .Net Remoting常用部署结构