commons-pool2对象池分配回收策略

来源:互联网 发布:淘宝客服外包多少钱 编辑:程序博客网 时间:2024/05/17 04:33

commons-pool2对象池分配回收策略

前一段时间写了一个rpc的客户端,使用apache commons-pool2的对象池管理长连接。其中由于没有完全弄清楚各个参数的意义,即其中分配回收策略,所以出现了一些不太合理的现象。最近详细看了源码,这里整理一下。

一、对象的创建

获取对象流程

1. 对象创建的时机

   对象的创建有两个时机,一个是应用像对象池获取对象时;另一个是空闲对象回收后,判断需要保证minIdle数量的对象并重新创建对象。关于第二点在回收时再说,先梳理一下获取对象的过程。

2. 对象创建的条件

   如上图所示,对象的创建如果满足下面的判断条件,则直接跳出,不会创建对象。判断条件中涉及到了两个值localMaxTotal 和newCreateCount ,localMaxTotal 就是对象池配置中的maxTotal(最大的对象个数);newCreateCount 表示当前对象池中对象的个数,每当创建一个对象这个数值加一,销毁一个对象这个数值减一。那么创建对象需要满足两个条件:

  1. 对象池中的对象没有达到上限maxTotal
  2. 对象池中的对象个数不能超过Integer类型的最大值
GenericObjectPool create方法中:if (localMaxTotal > -1 && newCreateCount > localMaxTotal ||                newCreateCount > Integer.MAX_VALUE) {            createCount.decrementAndGet();            return null;        }

3. 对象创建之后的工作

   对象创建之后还有一个分配的过程。这个过程会把对象的状态修改为ALLOCATED,初始时是IDLE。同时会记录此对象本次被借出的时间、被借出的次数。如下代码所示:

if (state == PooledObjectState.IDLE) {            state = PooledObjectState.ALLOCATED;            lastBorrowTime = System.currentTimeMillis();            lastUseTime = lastBorrowTime;            borrowedCount++;            if (logAbandoned) {                borrowedBy = new AbandonedObjectCreatedException();            }            return true;        }

   分配完之后还有一个激活和验证的过程,分别调用factory中自定义的activateObject和validateObject方法。如果激活和验证过程失败,则会销毁这个创建好的对象。

二、对象的回收

对象回收evict流程

1. 空闲对象回收线程的启动

    public GenericObjectPool(PooledObjectFactory<T> factory,            GenericObjectPoolConfig config) {        super(config, ONAME_BASE, config.getJmxNamePrefix());        if (factory == null) {            jmxUnregister(); // tidy up            throw new IllegalArgumentException("factory may not be null");        }        this.factory = factory;        idleObjects = new LinkedBlockingDeque<PooledObject<T>>(config.getFairness());        setConfig(config);        startEvictor(getTimeBetweenEvictionRunsMillis());    }

   其实即使没有最后面的startEvictor(getTimeBetweenEvictionRunsMillis()),还是会启动evict线程,因为setConfig方法也会间接启动evict线程。

   因为从config拷贝配置到对象池时,一定会拷贝timeBetweenEvictionRunsMillis参数,而对象池设置这个参数时就会启动evict线程

    public final void setTimeBetweenEvictionRunsMillis(            long timeBetweenEvictionRunsMillis) {        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;        startEvictor(timeBetweenEvictionRunsMillis);    }

2. 空闲对象回收线程的条件

   一个空闲对象是否被回收,关键取决于EvictionPolicy的evict方法,而DefaultEvictionPolicy是它的唯一实现:

    public boolean evict(EvictionConfig config, PooledObject<T> underTest,            int idleCount) {        if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&                config.getMinIdle() < idleCount) ||                config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {            return true;        }        return false;    }

   可以看到,有两个参数影响了是否回收的结果,config.getIdleSoftEvictTime()和config.getIdleEvictTime()。他们对应连接池配置的softMinEvictableIdleTimeMillis和minEvictableIdleTimeMillis参数,只是在判断对象是否可回收前做了一些特殊的处理,如果设置了负数值,则会被转换为Long的最大值:

    public EvictionConfig(long poolIdleEvictTime, long poolIdleSoftEvictTime,            int minIdle) {        if (poolIdleEvictTime > 0) {            idleEvictTime = poolIdleEvictTime;        } else {            idleEvictTime = Long.MAX_VALUE;        }        if (poolIdleSoftEvictTime > 0) {            idleSoftEvictTime = poolIdleSoftEvictTime;        } else {            idleSoftEvictTime  = Long.MAX_VALUE;        }        this.minIdle = minIdle;    }

   softMinEvictableIdleTimeMillis和minEvictableIdleTimeMillis的两个判断条件只要满足其中一条即表示此对象可被回收。它们的区别在于空闲时间大于softMinEvictableIdleTimeMillis时,空闲对象的数量一定要大于配置的minIdle才可以;而minEvictableIdleTimeMillis只要大于空闲时间就成立。
   所以,minEvictableIdleTimeMillis会暴力回收调空闲队列里的对象,不管空闲个数是否超过minIdle。而之后还会重新创建,导致对象被销毁又重建的现象发生。

3. 空闲对象回收线程其他的工作

   evict线程不光做回收对象的工作。
1. 刚刚提到,如果回收后发现空闲队列中的对象个数已经小于minIdle,则会创建对象至minIdle个。
2. 如果不满足回收条件,则可以检查对象可用性,取决于testWhileIdle参数。检查的过程是先激活、再验证,如果失败则销毁对象。

4. 对象的销毁途径

  1. 空闲对象直接被evict线程回收
  2. borrow、return、evict过程中对象的激活、验证、挂起等操作出现异常
  3. return对象时对象池关闭或者maxIdle到了上限
  4. 应用主动调用invalidateObject或clear方法
原创粉丝点击