百日筑基第三十一天-对象池复用实践
什么是对象池
对象池顾名思义就是存放对象的池,与我们常听到的线程池、数据库连接池、http连接池等一样,都是典型的池化设计思想。当需要创建对象时,先在池子中获取,如果池子中没有符合条件的对象,再进行创建新对象,同样,当对象需要销毁时,不做真正的销毁,而是将其setActive(false),并存入池子中。这样就避免了大量对象的创建。
对象池的优点: 可以集中管理池中对象,减少频繁创建和销毁长期使用的对象,从而提升复用性,以节约资源的消耗,可以有效避免频繁为对象分配内存和释放堆中内存,进而减轻jvm
垃圾收集器的负担,避免内存抖动。
对象池的缺点: 会生成脏对象,因为当对象被放回对象池后,还保留着刚刚被客户端调用时生成的数据。脏对象持有上次的使用,导致内存泄漏等问题。如果下一次使用时没有清理,可能影响程序的处理数据。脏对象的生命周期比普通对象长久。维持大量的对象池也比较占用内存空间。
设计对象池需要注意什么
【1】确保线程安全。
【2】合理设置池子大小。如果对象池没有限制,可能导致对象池持有过多的闲置对象,增加内存的占用。如果对象池闲置过小,没有可用的对象时,会造成之前对象池无可用的对象时,再次请求出现的问题。现在Java
的对象分配操作不比C语言的malloc
调用慢, 对于轻中量级的对象, 分配/释放对象的开销可以忽略不计,并发环境中, 多个线程可能(同时)需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高。对象池属于空间换时间的折中。
【3】制定合理的驱逐策略。
【4】确保对象归还后,外部没有地方持有该对象的引用。
common-pool2组件介绍
【1】Apache Common Pool2
是Apache
提供的一个通用对象池技术实现,可以方便定制化自己需要的对象池。
【2】Maven
依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${version}</version>
</dependency>
【3】使用案例
public class ByteArrayOutputStreamFactory extends BasePooledObjectFactory<ByteArrayOutputStream> {
private static final int DEFAULT_SIZE = 1024;
@Override
public ByteArrayOutputStream create() throws Exception {
return new ByteArrayOutputStream(DEFAULT_SIZE);
}
@Override
public PooledObject<ByteArrayOutputStream> wrap(ByteArrayOutputStream s) {
return new DefaultPooledObject<>(s);
}
@Override
public void activateObject(PooledObject<ByteArrayOutputStream> p) {
p.getObject().reset();
}
}
public class ByteArrayStreamPool {
private static final GenericObjectPool<ByteArrayOutputStream> POOL;
static {
// 根据需求自定义配置
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
cfg.setJmxNamePrefix("objectPool");
// 资源耗尽时,是否阻塞等待获取资源,默认 true
config.setBlockWhenExhausted(false);
// 回收资源线程的执行周期,默认 -1 表示不启用回收资源线程
config.setTimeBetweenEvictionRunsMillis(10000);
// 对象总数
cfg.setMaxTotal(sxInferConfig.getPoolMaxTotal());
// 最大空闲对象数
cfg.setMaxIdle(sxInferConfig.getPoolMaxIdle());
// 最小空闲对象数
cfg.setMinIdle(sxInferConfig.getPoolMinIdle());
// 借对象阻塞最大等待时间
// 获取资源的等待时间。blockWhenExhausted 为 true 时有效。-1 代表无时间限制,一直阻塞直到有可用的资源
cfg.setMaxWaitMillis(sxInferConfig.getPoolMaxWait());
// 最小驱逐空闲时间
cfg.setMinEvictableIdleTimeMillis(sxInferConfig.getPoolMinEvictableIdleTimeMillis());
// 每次驱逐数量 资源回收线程执行一次回收操作,回收资源的数量。默认 3
cfg.setNumTestsPerEvictionRun(sxInferConfig.getPoolNumTestsPerEvictionRun());
POOL = new GenericObjectPool<>(new ByteArrayOutputStreamFactory(), config);
}
private ByteArrayStreamPool() {
}
public static GenericObjectPool<ByteArrayOutputStream> get() {
return POOL;
}
}
// 注意:pool须定义为单例(不要频繁创建对象池,这比频繁创建对象更糟糕)
GenericObjectPool<ByteArrayOutputStream> pool = ByteArrayStreamPool.get();
ByteArrayOutputStream stream = null;
try {
stream = pool.borrowObject();
} catch (NoSuchElementException ex) {
// 自行创建一个对象
stream = new ByteArrayOutputStream(1024);
}
// do something
try {
// 自行创建的对象returnObject会抛出异常
pool.returnObject(stream);
} catch(Exception ex) {
}