1 采用对象池的原因
Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除。因此,对象的生命周期长度可用如下的表达式表示:T = T1 + T2 +T3.其中T1表示对象的创建时间,T2表示对象的使用时间,而T3则表示其清除时间。由此,我们可以看出,只有T2是真正有效的时间,而T1、T3则是对象本身的开销。下面再看看T1、T3在对象的整个生命周期中所占的比例。
我们知道,Java对象是通过构造函数来创建的,在这一过程中,该构造函数链中的所有构造函数也都会被自动调用。另外,默认情况下,调用类的构造函数时,Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和double变量设置成0.0,逻辑值设置成false.所以用new关键字来新建一个对象的时间开销是很大的,如表1所示。
表1 一些操作所耗费时间的对照表
.
从表1可以看出,新建一个对象需要980个单位的时间,是本地赋值时间的980倍,是方法调用时间的166倍,而若新建一个数组所花费的时间就更多了。
2.通常我们如此实现一个业务对象池,实现org.apache.commons.pool2.的一些接口。
/*
- @author zhangshuo
*/
@Slf4j
public class ResourceLoaderFactory extends BasePooledObjectFactory {
/**
* 单例工厂
*
* @return
*/
public static ResourceLoaderFactory getInstance() {
return ResourceFactoryHolder.resourceLoaderFactory;
}
private static class ResourceLoaderFactoryHolder {
public final static ResourceLoaderFactory resourceLoaderFactory= new ResourceLoaderFactory();
}
private final GenericObjectPool<ResourceLoader> objectsPool;
public ResourceLoaderFactory() {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(1024);
config.setMaxIdle(50);
config.setMinIdle(8);
// 当Pool中没有对象时不等待,而是直接new个新的
config.setBlockWhenExhausted(false);
AbandonedConfig abandonConfig = new AbandonedConfig();
abandonConfig.setRemoveAbandonedTimeout(300);
abandonConfig.setRemoveAbandonedOnBorrow(true);
abandonConfig.setRemoveAbandonedOnMaintenance(true);
objectsPool = new GenericObjectPool<ResourceLoader>(this, config, abandonConfig);
}
public ResourceLoader takeResourceLoader() throws Exception {
try {
return objectsPool.borrowObject();
} catch (Exception e) {
log.error("ResourceLoader error:{}",e);
}
return create();
}
@Override
public ResourceLoader create() throws Exception {
return ResourceLoader.builder().build();
}
@Override
public PooledObject<ResourceLoader> wrap(ResourceLoader obj) {
return new DefaultPooledObject<ResourceLoader>(obj);
}
public void returnObject(ResourceLoader loader) {
loader.reset();
objectsPool.returnObject(loader);
}
}
3.对象池的优点
复用池中对象,消除创建对象、回收对象 所产生的内存开销、cpu开销以及(若跨网络)产生的网络开销.
常见的使用对象池有:在使用socket时(包括各种连接池)、线程等等
4.对象池的缺点:
现在Java的对象分配操作不比c语言的malloc调用慢, 对于轻中量级的对象, 分配/释放对象的开销可以忽略不计;
并发环境中, 多个线程可能(同时)需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;
由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;
很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高,
对象池有其特定的适用场景:
受限的, 不需要可伸缩性的环境(cpu\内存等物理资源有限): cpu性能不够强劲, 内存比较紧张, 垃圾收集, 内存抖动会造成比较大的影响, 需要提高内存管理效率, 响应性比吞吐量更为重要;
数量受限的资源, 比如数据库连接;