java池化
以下图片来源于http://commons.apache.org/pool/guide/sequencediagrams.html,加了点注释而已
所有的时间都是毫秒,并且 GenericObjectPool 是 thread safe,以下分别说明各个参数:
whenExhaustedAction
WHEN_EXHAUSTED_BLOCK
参考 maxWait
WHEN_EXHAUSTED_FAIL
WHEN_EXHAUSTED_GROW
maxWait
如果 whenExhaustedAction = WHEN_EXHAUSTED_BLOCK,那么 borrow 不能立即返回 Object时,
会wait,知道得到池化对象,或者超过 maxWait。如果maxWait < 1,那么 会一直等到获得池化对象
lifo
true/false
testOnBorrow
GenericObjectPool.borrowObject() 时 是否调用 PoolableObjectFactory.validateObject
testOnReturn
GenericObjectPool.returnObject() 时 是否调用 PoolableObjectFactory.validateObject
maxIdle 默认值为 DEFAULT_MAX_IDLE=8,超过该值的 idle 对象会被destory
基本上看到这里就能用了,最好别用 evictor,除非要求..... 例如 db长连接、心跳
timeBetweenEvictionRunsMillis
启动evictor thread的间隔,如果设置为<1不会启动evictor thread
evictor thread 按照规则下面定义来 填充/移除 池中的对象
evictor thread 在执行检查时会阻塞 borrow/return,因此如果该参数设置的很小,会造成evictor thread频繁执行而影响性能
Eviction runs require an exclusive synchronization
lock on the pool, so if they run too frequently and / or incur excessive
latency when creating, destroying or validating object instances,
performance issues may result
minIdle
如果evictor启动,evictor检查到池中的对象小于该值时,会创建min(minIdle, numActive + numIdle - maxActive) 的对象来填充池。小心使用该变量,设置为 <1最安全,可能导致dbcp死锁:dbcp-44,在common-pool 1.5 dbcp1.4中已经修正了这个问题,4年
numTestsPerEvictionRun
如果evictor启动,evictor每次启动时会检查池中的 x 个对象
numTestsPerEvictionRun > 0, x = min(_numTestsPerEvictionRun, GenericObjectPool.getNumIdle())
numTestsPerEvictionRun < 0, x = ceil( GenericObjectPool.getNumIdle())/abs(numTestsPerEvictionRun) )
i.e. abs(numTestsPerEvictionRun) 分之一的空闲对象会被检查
minEvictableIdleTimeMillis
如果evictor启动,evictor每次检查时可能会将池中空闲太久的对象移除池
如果设置为 < 1,是否把 idle 对象移除要看 softMinEvictableIdleTimeMillis
如果设置为 > 0,那么会把闲置了 minEvictableIdleTimeMillis 的对象PoolableObjectFactory.destroyObject掉
softMinEvictableIdleTimeMillis
如果evictor启动,并且minEvictableIdleTimeMillis < 1 或者按照 minEvictableIdleTimeMillis 计算对象不需要移出池
如果设置为 < 1,则不会移除任何idle对象
如果设置为 > 0,则当对象闲置的时间超过softMinEvictableIdleTimeMillis 且 GenericObjectPool.getNumIdle > minIdle 时 将对象移除池
一般应该把minEvictableIdleTimeMillis 配置为 -1,把softMinEvictableIdleTimeMillis配置为整数
_testWhileIdle
如果evictor启动,该变量为true则调用 PoolableObjectFactory.validateObject
备注1,minEvictableIdleTimeMillis 和 minEvictableIdleTimeMillis 的使用方法
if ((getMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getMinEvictableIdleTimeMillis())) {
removeObject = true;
} else if ((getSoftMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getSoftMinEvictableIdleTimeMillis()) &&
((getNumIdle() + 1)> getMinIdle())) { // +1 accounts for object we are processing
removeObject = true;
创建新的对象并初始化的操作,可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作(例如,从一台位于20,000千米以外的主机上读出一些数据)的时候,尤其是这样。在需要大量生成这样的对象的时候,就可能会对性能造成一些不可忽略的影响。要缓解这个问题,除了选用更好的硬件和更棒的虚拟机以外,适当地采用一些能够减少对象创建次数的编码技巧,也是一种有效的对策。对象池化技术(Object Pooling)就是这方面的著名技巧,而Jakarta Commons Pool组件则是处理对象池化的得力外援。
对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”(Object Pool,或简称Pool)。
对于没有状态的对象(例如String),在重复使用之前,无需进行任何处理;对于有状态的对象(例如StringBuffer),在重复使用之前,就需要把它们恢复到等同于刚刚生成时的状态。由于条件的限制,恢复某个对象的状态的操作不可能实现了的话,就得把这个对象抛弃,改用新创建的实例了。
并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。
Jakarta Commons Pool是一个用于在Java程序中实现对象池化的组件。它的基本情况是:
- 主要作者:Morgan Delagrange、Geir Magnusson、Craig McClanahan、Rodney Waldhoff、David Weinrich和Dirk Verbeeck
- 最新版本:1.1
- 所含包数:2个(org.apache.commons.pool和org.apache.commons.pool.impl)
- 所含类数:21个(其中有4个抽象类和6个接口)
- 适用平台:Java 2, Standard Edition.
- 单纯地使用Pool组件不需要太多的Java 2的知识和经验,对语法和基本概念(对象、异常、类、接口、实例、继承和实现等)有一般了解即可。
为了顺利的按照本文中提到的方法使用Pool组件,除去Java 2 SDK外,还需要先准备下列一些东西:
- Jakarta Commons Pool
- 所需版本:1.0.1+
- 下载地址: http://jakarta.apache.org/commons/pool
- 作用:处理对象池化
- Jakarta Commons Collections
- 所需版本:2.1+
- 下载地址: http://jakarta.apache.org/commons/collections
- 作用:支持Jakarta Commons Pool的运行
以上两种软件均有已编译包和源代码包两种形式可供选择。一般情况下,使用已编译包即可。不过建议同时也下载源代码包,作为参考资料使用。
如果打算使用源代码包自行编译,那么还需要准备以下一些东西:
- Ant
- 所需版本:1.5.3+
- 下载地址: http://ant.apache.org
- 作用:运行编译用脚本
- JUnit
- 所需版本:3.8.1+
- 下载地址: http://www.junit.org
- 作用:编译和运行单元测试
具体的编译方法,可以参看有关的Ant文档。
将解压或编译后得到的commons-pool.jar和commons-collections.jar放入CLASSPATH,就可以开始使用Pool组件了。
PoolableObjectFactory、ObjectPool和ObjectPoolFactory
在Pool组件中,对象池化的工作被划分给了三类对象:
- PoolableObjectFactory用于管理被池化的对象的产生、**、挂起、校验和销毁;
- ObjectPool用于管理要被池化的对象的借出和归还,并通知PoolableObjectFactory完成相应的工作;
- ObjectPoolFactory则用于大量生成相同类型和设置的ObjectPool。
相应地,使用Pool组件的过程,也大体可以划分成“创立PoolableObjectFactory”、“使用ObjectPool”和可选的“利用ObjectPoolFactory”三种动作。
Pool组件利用PoolableObjectFactory来照看被池化的对象。ObjectPool的实例在需要处理被池化的对象的产生、**、挂起、校验和销毁工作时,就会调用跟它关联在一起的PoolableObjectFactory实例的相应方法来操作。
PoolableObjectFactory是在org.apache.commons.pool包中定义的一个接口。实际使用的时候需要利用这个接口的一个具体实现。Pool组件本身没有包含任何一种PoolableObjectFactory实现,需要根据情况自行创立。
创立PoolableObjectFactory的大体步骤是:
- 创建一个实现了PoolableObjectFactory接口的类。
import org.apache.commons.pool.PoolableObjectFactory; public class PoolableObjectFactorySample implements PoolableObjectFactory { private static int counter = 0; }
- 为这个类添加一个Object makeObject()方法。这个方法用于在必要时产生新的对象。
public Object makeObject() throws Exception { Object obj = String.valueOf(counter++); System.err.println("Making Object " + obj); return obj; }
- 为这个类添加一个void activateObject(Object obj)方法。这个方法用于将对象“**”――设置为适合开始使用的状态。
public void activateObject(Object obj) throws Exception { System.err.println("Activating Object " + obj); }
- 为这个类添加一个void passivateObject(Object obj)方法。这个方法用于将对象“挂起”――设置为适合开始休眠的状态。
public void passivateObject(Object obj) throws Exception { System.err.println("Passivating Object " + obj); }
- 为这个类添加一个boolean validateObject(Object obj)方法。这个方法用于校验一个具体的对象是否仍然有效,已失效的对象会被自动交给destroyObject方法销毁
public boolean validateObject(Object obj) { boolean result = (Math.random() > 0.5); System.err.println("Validating Object " + obj + " : " + result); return result; }
- 为这个类添加一个void destroyObject(Object obj)方法。这个方法用于销毁被validateObject判定为已失效的对象。
public void destroyObject(Object obj) throws Exception { System.err.println("Destroying Object " + obj); }
最后完成的PoolableObjectFactory类似这个样子:
PoolableObjectFactorySample.java |
import org.apache.commons.pool.PoolableObjectFactory; public class PoolableObjectFactorySample implements PoolableObjectFactory { private static int counter = 0; public Object makeObject() throws Exception { Object obj = String.valueOf(counter++); System.err.println("Making Object " + obj); return obj; } public void activateObject(Object obj) throws Exception { System.err.println("Activating Object " + obj); } public void passivateObject(Object obj) throws Exception { System.err.println("Passivating Object " + obj); } public boolean validateObject(Object obj) { /* 以1/2的概率将对象判定为失效 */ boolean result = (Math.random() > 0.5); System.err.println("Validating Object " + obj + " : " + result); return result; } public void destroyObject(Object obj) throws Exception { System.err.println("Destroying Object " + obj); } } |
有了合适的PoolableObjectFactory之后,便可以开始请出ObjectPool来与之同台演出了。
ObjectPool是在org.apache.commons.pool包中定义的一个接口,实际使用的时候也需要利用这个接口的一个具体实现。Pool组件本身包含了若干种现成的ObjectPool实现,可以直接利用。如果都不合用,也可以根据情况自行创建。具体的创建方法,可以参看Pool组件的文档和源码。
ObjectPool的使用方法类似这样:
- 生成一个要用的PoolableObjectFactory类的实例。
PoolableObjectFactory factory = new PoolableObjectFactorySample();
- 利用这个PoolableObjectFactory实例为参数,生成一个实现了ObjectPool接口的类(例如StackObjectPool)的实例,作为对象池。
ObjectPool pool = new StackObjectPool(factory);
- 需要从对象池中取出对象时,调用该对象池的Object borrowObject()方法。
Object obj = null; obj = pool.borrowObject();
- 需要将对象放回对象池中时,调用该对象池的void returnObject(Object obj)方法。
pool.returnObject(obj);
- 当不再需要使用一个对象池时,调用该对象池的void close()方法,释放它所占据的资源。
pool.close();
这些操作都可能会抛出异常,需要另外处理。
比较完整的使用ObjectPool的全过程,可以参考这段代码:
ObjectPoolSample.java |
import org.apache.commons.pool.ObjectPool; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.StackObjectPool; public class ObjectPoolSample { public static void main(String[] args) { Object obj = null; PoolableObjectFactory factory = new PoolableObjectFactorySample(); ObjectPool pool = new StackObjectPool(factory); try { for(long i = 0; i < 100 ; i++) { System.out.println("== " + i + " =="); obj = pool.borrowObject(); System.out.println(obj); pool.returnObject(obj); } obj = null;//明确地设为null,作为对象已归还的标志 } catch (Exception e) { e.printStackTrace(); } finally { try{ if (obj != null) {//避免将一个对象归还两次 pool.returnObject(obj); } pool.close(); } catch (Exception e){ e.printStackTrace(); } } } } |
另外,ObjectPool接口还定义了几个可以由具体的实现决定要不要支持的操作,包括:
void clear()
清除所有当前在此对象池中休眠的对象。
int getNumActive()
返回已经从此对象池中借出的对象的总数。
int getNumIdle()
返回当前在此对象池中休眠的对象的数目。
void setFactory(PoolableObjectFactory factory)
将当前对象池与参数中给定的PoolableObjectFactory相关联。如果在当前状态下,无法完成这一操作,会有一个IllegalStateException异常抛出。
有时候,要在多处生成类型和设置都相同的ObjectPool。如果在每个地方都重写一次调用相应构造方法的代码,不但比较麻烦,而且日后修改起来,也有所不便。这种时候,正是使用ObjectPoolFactory的时机。
ObjectPoolFactory是一个在org.apache.commons.pool中定义的接口,它定义了一个称为ObjectPool createPool()方法,可以用于大量生产类型和设置都相同的ObjectPool。
Pool组件中,对每一个ObjectPool实现,都有一个对应的ObjectPoolFactory实现。它们相互之间,有一一对应的参数相同的构造方法。使用的时候,只要先用想要的参数和想用的ObjectPoolFactory实例,构造出一个ObjectPoolFactory对象,然后在需要生成ObjectPool的地方,调用这个对象的createPool()方法就可以了。日后无论想要调整所用ObjectPool的参数还是类型,只需要修改这一处,就可以大功告成了。
将 《使用ObjectPool》一节中的例子,改为使用ObjectPoolFactory来生成所用的ObjectPool对象之后,基本就是这种形式:
ObjectPoolFactorySample.java |
import org.apache.commons.pool.ObjectPool; import org.apache.commons.pool.ObjectPoolFactory; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.StackObjectPoolFactory; public class ObjectPoolFactorySample { public static void main(String[] args) { Object obj = null; PoolableObjectFactory factory = new PoolableObjectFactorySample(); ObjectPoolFactory poolFactory = new StackObjectPoolFactory(factory); ObjectPool pool = poolFactory.createPool(); try { for(long i = 0; i < 100 ; i++) { System.out.println("== " + i + " =="); obj = pool.borrowObject(); System.out.println(obj); pool.returnObject(obj); } obj = null; } catch (Exception e) { e.printStackTrace(); } finally { try{ if (obj != null) { pool.returnObject(obj); } pool.close(); } catch (Exception e){ e.printStackTrace(); } } } } |
PoolableObjectFactory定义了许多方法,可以适应多种不同的情况。但是,在并没有什么特殊需要的时候,直接实现PoolableObjectFactory接口,就要编写若干的不进行任何操作,或是始终返回true的方法来让编译通过,比较繁琐。这种时候就可以借助BasePoolableObjectFactory的威力,来简化编码的工作。
BasePoolableObjectFactory是org.apache.commons.pool包中的一个抽象类。它实现了PoolableObjectFactory接口,并且为除了makeObject之外的方法提供了一个基本的实现――activateObject、passivateObject和destroyObject不进行任何操作,而validateObject始终返回true。通过继承这个类,而不是直接实现PoolableObjectFactory接口,就可以免去编写一些只起到让编译通过的作用的代码的麻烦了。
这个例子展示了一个从BasePoolableObjectFactory扩展而来的PoolableObjectFactory:
BasePoolableObjectFactorySample.java |
import org.apache.commons.pool.BasePoolableObjectFactory; public class BasePoolableObjectFactorySample extends BasePoolableObjectFactory { private int counter = 0; public Object makeObject() throws Exception { return String.valueOf(counter++); } } |
可口可乐公司的软饮料有可口可乐、雪碧和芬达等品种,百事可乐公司的软饮料有百事可乐、七喜和美年达等类型,而Pool组件提供的ObjectPool实现则有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等种类。
不同类型的软饮料各有各自的特点,分别适应不同消费者的口味;而不同类型的ObjectPool也各有各自的特色,分别适应不同的情况。
StackObjectPool利用一个java.util.Stack对象来保存对象池里的对象。这种对象池的特色是:
- 可以为对象池指定一个初始的参考大小(当空间不够时会自动增长)。
- 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
- 可以为对象池指定一个可保存的对象数目的上限。达到这个上限之后,再向池里送回的对象会被自动送去回收。
StackObjectPool的构造方法共有六个,其中:
- 最简单的一个是StackObjectPool(),一切采用默认的设置,也不指明要用的PoolableObjectFactory实例。
- 最复杂的一个则是StackObjectPool(PoolableObjectFactory factory, int max, int init)。其中:
- 参数factory指明要与之配合使用的PoolableObjectFactory实例;
- 参数max设定可保存对象数目的上限;
- 参数init则指明初始的参考大小。
- 剩余的四个构造方法则是最复杂的构造方法在某方面的简化版本,可以根据需要选用。它们是:
- StackObjectPool(int max)
- StackObjectPool(int max, int init)
- StackObjectPool(PoolableObjectFactory factory)
- StackObjectPool(PoolableObjectFactory factory, int max)
用不带factory参数的构造方法构造的StackObjectPool实例,必须要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。
这种对象池可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。
SoftReferenceObjectPool利用一个java.util.ArrayList对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(Soft Reference)。这种对象池的特色是:
- 可以保存任意多个对象,不会有容量已满的情况发生。
- 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
- 可以在初始化同时,在池内预先创建一定量的对象。
- 当内存不足的时候,池中的对象可以被Java虚拟机回收。
SoftReferenceObjectPool的构造方法共有三个,其中:
- 最简单的是SoftReferenceObjectPool(),不预先在池内创建对象,也不指明要用的PoolableObjectFactory实例。
- 最复杂的一个则是SoftReferenceObjectPool(PoolableObjectFactory factory, int initSize)。其中:
- 参数factory指明要与之配合使用的PoolableObjectFactory实例
- 参数initSize则指明初始化时在池中创建多少个对象。
- 剩下的一个构造方法,则是最复杂的构造方法在某方面的简化版本,适合在大多数情况下使用。它是:
- SoftReferenceObjectPool(PoolableObjectFactory factory)
用不带factory参数的构造方法构造的SoftReferenceObjectPool实例,也要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。
这种对象池也可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。
GenericObjectPool利用一个org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。这种对象池的特色是:
- 可以设定最多能从池中借出多少个对象。