前言
GenericObjectPool 是 Apache Commons Pool 库的一部分,它提供了一个通用的对象池实现
,允许用户在需要时从池中借用和返回对象
,而不是每次需要一个新实例时都创建一个。这种方法可以显著提高性能
,特别是对于创建开销大或需要频繁使用的对象
。
哪些对象创建、销毁成本较高
创建和销毁成本较大的 对象有哪些、原因是啥:
-
数据库连接:建立数据库连接
涉及网络通讯
以及数据库服务器上的资源分配和初始化
。这些过程可能需要消耗显著的时间和系统资源
。因此,数据库连接池
(如 HikariCP、c3p0)通常用于管理这些连接,以减少每次操作所需的创建和销毁开销。 -
线程:
创建和销毁线程涉及操作系统级的资源分配
,这可能是一个高代价的操作,尤其是在需要频繁创建和销毁大量线程的场景中。Java 的 ExecutorService 提供了线程池功能
,可以有效地复用线程
来处理多个任务。 -
网络连接:建立网络连接(如 TCP 连接)
涉及网络协议的握手过程
,这可能导致显著的延迟和资源消耗。复用网络连接(例如通过HTTP 连接池
)可以减少这种开销。 -
大对象或复杂对象:创建一些
内存占用大
或构造过程复杂
的对象也可能涉及较高的开销。例如,需要加载大量数据或执行复杂初始化逻辑的对象。 -
外部资源(如
文件或其他系统资源
):操作文件或与外部系统交互(通过 API 调用等)可能涉及显著的资源开销。缓存操作结果
或重用外部连接
可以帮助降低这种开销。
为了管理资源创建和销毁的成本,通常采用以下策略
:
对象池化
(Pooling):如数据库连接池、线程池和 HTTP 客户端连接池
,通过重用现有对象来减少创建和销毁的频率。缓存
(Caching):缓存是另一种减少资源获取成本的策略,特别是当资源获取涉及昂贵的计算或外部系统调用时。延迟初始化
(Lazy Initialization):延迟(或按需)创建资源,直到首次使用时,可以推迟资源分配的开销,并可能省略对某些永远不会使用的资源的开销。
对象创建的步骤
在Java中,创建对象时的内存分配过程主要发生在堆(Heap)内存
区域。整个过程受Java虚拟机(JVM)的垃圾收集(GC)
算法和内存模型
的管理。这个过程可以分为以下几个步骤:
-
类加载检查
当代码尝试创建一个对象实例时(如通过new XXXClassName()
),JVM首先检查这个类是否已被加载、链接和初始化
。如果没有,系统会执行这些步骤。 -
为对象分配内存
一旦类检查完成,JVM会在堆内存中为新对象分配内存
。分配的内存大小根据对象的类定义来确定
,假如类有String name、Integer age、Boolean isGirl,包括所有实例变量的大小
及其他一些开销(如对象头信息)。这部分内存用于存储对象的状态(即字段或属性的值
)。内存分配方式
取决于垃圾收集器的具体实现。常见的分配方式包括:
a.指针碰撞
(Bump the Pointer):如果堆内存是整齐划分的
(使用的垃圾收集算法如Minor GC后),那么对象的分配仅仅是将指向堆中的空闲区域的指针向上移动对象大小的距离
。
b. 空闲列表(Free List):如果堆内存是分散的
(即存在被使用和未被使用的内存区域),JVM必须通过遍历一个列表来找到足够大的空间
来存放新对象。 -
初始化内存空间
内存分配完毕后,JVM将分配的内存空间初始化为零值
(不包括对象头),确保对象的实例变量不会有Java默认值以外的任何值
。对象头
一般包含对象的哈希码
、GC代年龄
等信息。 -
设置对象头
JVM设置对象头信息,包括这个对象是哪个类的实例
、对象的哈希码
和锁信息等。 -
执行构造方法
最后,JVM调用对象的构造函数。构造函数方法可能会对对象变量进行初始化,这可能会覆盖在第3步中设置的零值
。
这一过程尽管看起来复杂,但在现代虚拟机中非常高效。JVM的设计包括了多种优化,如线程本地内存分配(TLAB),这允许每个线程在自己的内存缓冲区中分配对象
,从而减少线程间的竞争
。
为什么使用 GenericObjectPool?
当创建一个对象的成本(时间、资源等)很高时,就可以使用对象池。对象池通过重用现有的对象
而不是每次需要时都创建新对象来提高性能和资源利用率
。GenericObjectPool 提供了管理这种对象池的功能
,包括创建、借用、返回以及销毁对象的机制
。
GenericObjectPool 的关键特性:
线程安全
:GenericObjectPool 是线程安全的,可以在多线程环境中共享使用。- 可配置性:提供了许多配置项,如
最大对象数、最大空闲对象数、最小空闲对象数、当池中对象耗尽时的行为
等。 泛型支持
:它是泛型的,可以指定池中管理的对象类型。
原理和工作流程:
- 对象创建:
GenericObjectPool 使用一个工厂模式
来创建对象。它依赖于实现了 PooledObjectFactory 接口的工厂类来创建、初始化、激活、钝化和销毁对象。 借用对象 (borrowObject)
:
当调用 borrowObject() 方法时,池尝试为请求提供一个实例。首先,它会检查池中是否有可用的空闲对象(已创建但未被借用的对象)。
如果有空闲对象,池将返回一个
,并将其标记为“已借用”
。- 如果
没有空闲对象可用
,但当前池中对象的总数未达到配置的最大数量
(maxTotal),池将使用工厂创建一个新对象
。 如果池已满
(即,当前借用的对象数加上空闲对象数达到了 maxTotal),则 borrowObject() 方法的行为取决于配置的阻塞策略
。它可以抛出异常
、阻塞等待直到有对象可用
,或者返回 null
,具体取决于配置。
返回对象 (returnObject)
:
使用完对象后,客户端应该通过调用 returnObject() 方法来归还对象到池中。
归还操作通常涉及将对象标记为“空闲”
,可能还会涉及钝化(重置或清理)对象
以供将来重用。对象销毁destroyObject()
:
池可以根据配置策略(如空闲时间超过一定限制
、空闲对象超过一定数量
等)决定销毁一些对象。
销毁操作由工厂的 destroyObject() 方法完成。维护活动
:
GenericObjectPool 可以配置为执行后台维护线程,这个线程定期检查池中的对象
,根据配置执行对象的逐出
(比如基于空闲时间或其他策略来逐出对象),确保池中保持活跃和清理的对象
。
关键配置参数:
- maxTotal:池中允许的
最大对象数
。 - maxIdle:池中允许的
最大空闲对象数
。 - minIdle:池中确保的
最小空闲对象数
。 - maxWaitMillis:当池中没有可用对象时,borrowObject() 方法
等待对象变为可用的最大时间
(以毫秒
为单位)。超时则抛出异常。 - testOnBorrow、testOnReturn、testWhileIdle:
是否在借用、归还、空闲时检验对象
。
快速开始
在pom.xml 文件中,添加如下依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
实现 池化对象工厂接口 PooledObjectFactory
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> {
@Override
public void activateObject(PooledObject<Context> pooledObject) throws Exception {
// 激活对象:当这个对象下次从池中借用时,将调用 activateObject() 方法使其重新“激活”。激活通常包括将对象恢复到适合再次使用的状态
pooledObject.getObject().init();
}
@Override
public void destroyObject(PooledObject<Context> pooledObject) throws Exception {
}
@Override
public PooledObject<Context> makeObject() throws Exception {
// 创建新对象
Context context = new Context();
context.init();
return new DefaultPooledObject<>(context);
}
@Override
public void passivateObject(PooledObject<Context> pooledObject) throws Exception {
// 钝化对象:重置对象的状态,使其回到一个干净的初始状态,包括决定是否将对象的属性设置为 null 或重置为默认值
}
@Override
public 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,设置工厂类、池化配置
*/
@PostConstruct
public 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));
// 资源耗尽时,是否阻塞等待获取资源,默认 true
cfg.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);
}
}
调用案例
@Resource
private 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);
}
}
}