1.背景
最近查看了下Apache commons-pool2的源代碼commons-pool2-2.4.2,代碼不多,大概50個java類左右,閱讀源代碼的初衷是爲了通過不斷的學習和總結,昇華自己的技術能力,寫此博客是爲了給自己留下一點筆記,日後能夠溫故知新。
Apache commons-pool2類庫是對象池技術的一種具體實現,它的出現是爲了解決頻繁的創建和銷燬對象帶來的性能損耗問題,原理就是建立一個對象池,池中預先生成了一些對象,需要對象的時候進行租借,用完後進行歸還,對象不夠時靈活的自動創建,對象池滿後提供參數控制是否阻塞還是非阻塞響應租借.
熟悉了Apache commons-pool2對於瞭解數據庫連接池DBCP和了解redis客戶端jedis的連接池都有很大幫助,因爲jedis的連接池就是基於Apache commons-pool2實現,而DBCP是基於Jakarta commons-pool實現。
2.Apache commons-pool2中3類重要接口
- PooledObjectFactory/KeyedPooledObjectFactory:是兩個接口,作用都是產生PooledObject的工廠,定義瞭如何makeObject創建、destroyObject銷燬、validateObject校驗、activateObject激活PooledObject對象,使用Apache commons-pool2的使用者需要自己實現這個接口
- PooledObject:是一個接口,定義了getCreateTime獲取PooledObject創建時間,getActiveTimeMillis獲取PooledObject處於激活狀態的時間,getIdleTimeMillis獲取PooledObject空閒時間,getLastBorrowTime獲取PooledObject最近借出時間,getLastReturnTime獲取PooledObject最近歸還時間,getLastUsedTime獲取PooledObject最近使用時間。目前Apache commons-pool2提供了2個默認實現DefaultPooledObject和PooledSoftReference,一般使用DefaultPooledObject即可
- ObjectPool/KeyedObjectPool:是兩個接口,作用都是管理池裏面的PooledObject,borrowObject借出PooledObject,returnObject歸還PooledObject,invalidateObject調用PooledObjectFactory銷燬PooledObject,addObject調用PooledObjectFactory創建PooledObject,getNumIdle給出PooledObject空閒個數,getNumActive給出PooledObject激活的個數,使用Apache commons-pool2的使用者可以使用默認的5個實現(SoftReferenceObjectPool GenericObjectPool ProxiedObjectPool GenericKeyedObjectPool ProxiedKeyedObjectPool),也可以自己實現
其中PooledObjectFactory PooledObject ObjectPool關係和作用如下圖:
其中KeyedPooledObjectFactory PooledObject KeyedObjectPool關係和作用如下圖:
3.PooledObject接口類繼承關係OOM圖
PooledObject:是一個接口,定義了getCreateTime獲取PooledObject創建時間,getActiveTimeMillis獲取PooledObject處於激活狀態的時間,getIdleTimeMillis獲取PooledObject空閒時間,getLastBorrowTime獲取PooledObject最近借出時間,getLastReturnTime獲取PooledObject最近歸還時間,getLastUsedTime獲取PooledObject最近使用時間。目前Apache commons-pool2提供了2個默認實現DefaultPooledObject和PooledSoftReference,一般使用DefaultPooledObject即可
4.對象池接口ObjectPool和KeyedObjectPool類關係
Apache commons-pool2裏有針對對象池的5個具體實現類SoftReferenceObjectPool GenericObjectPool ProxiedObjectPool GenericKeyedObjectPool ProxiedKeyedObjectPool ,這5個對象池按照實現的接口不同分爲2類,一個是實現了接口ObjectPool,另一個是實現了接口KeyedObjectPool
4.1 PowerDesigner反向工程ObjectPool的OOM關係圖
- GenericObjectPool:可配置LIFO/FIFO行爲的ObjectPool的實現。默認採用LIFO隊列方式。這意味着當有閒置的可用對象在對象池中時,borrowObject方法會返回最近的實例。如果配置文件中的LIFO配置項的值爲false,則將返回相反排序的實例,也就是會返回最先進入對象池的對象的實例;
- SoftReferenceObjectPool:使用LIFO行爲實現的ObjectPool。此外,在這個對象池實現中,每個對象都會被包裝到一個SoftReference中。SoftReference允許垃圾回收機制在需要釋放內存時回收對象池中的對象;
- ProxiedObjectPool:使用CGLIB或者JDK自帶動態代理技術,代理由GenericObjectPool或者SoftReferenceObjectPool產生的ObjectPool對象
4.2 PowerDesigner反向工程KeyedObjectPool的OOM關係圖
- GenericKeyedObjectPool:可配置LIFO/FIFO行爲的ObjectPool的實現。默認採用LIFO隊列方式。這意味着當有閒置的可用對象在對象池中時,borrowObject方法會返回最近的實例。如果配置文件中的LIFO配置項的值爲false,則將返回相反排序的實例,也就是會返回最先進入對象池的對象的實例;
- ProxiedKeyedObjectPool:使用CGLIB或者JDK自帶動態代理技術,代理由GenericKeyedObjectPool產生的ObjectPool對象
5. PooledObjectState類枚舉的PooledObject狀態及其轉換關係
狀態 | 描述 |
IDLE | 位於隊列中,未使用 |
ALLOCATED | 在使用 |
EVICTION | 位於隊列中,當前正在測試,可能會被回收 |
EVICTION_RETURN_TO_HEAD |
不在隊列中,當前正在測試,可能會被回收。 如果客戶端試圖借出該正在被測試的對象, 需等到測試完畢後才能借出並且從隊列中移除; 待測試完畢後,如果沒被回收該對象會放置在隊列的開頭位置。 |
VALIDATION | 位於隊列中,當前正在驗證 |
VALIDATION_PREALLOCATED |
不在隊列中,當前正在驗證。當對象從池中被借出, 在配置了testOnBorrow的情況下,對像從隊列移除和進行預分配的時候會進行驗證 |
VALIDATION_RETURN_TO_HEAD |
不在隊列中,正在進行驗證。從池中借出對象時, 從隊列移除對象時會先進行測試。返回到隊列頭部的時候應該做一次完整的驗證 |
INVALID | 回收或驗證失敗,將銷燬 |
ABANDONED | 即將無效 |
RETURNING | 返還到池中 |
6.GenericObjectPool的BorrowObject和ReturnObject流程分析
這裏先來一張GenericObjectPool的組圖,如下:
6.1 GenericObjectPool,BorrowObject租借池化對象 6.2 GenericObjectPool.ReturnObject歸還池化對象
7.GenericObjectPoolConfig配置屬性介紹
GenericObjectPoolConfig是一個配置類,提供了很多參數來控制對象池的相關屬性,如果你使用過jedis,對於其JedisPoolConfig可能不陌生
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="2048" /> <property name="maxIdle" value="200" /> <property name="numTestsPerEvictionRun" value="1024" /> <property name="timeBetweenEvictionRunsMillis" value="30000" /> <property name="minEvictableIdleTimeMillis" value="-1" /> <property name="softMinEvictableIdleTimeMillis" value="10000" /> <property name="maxWaitMillis" value="1500" /> <property name="testOnBorrow" value="true" /> <property name="testWhileIdle" value="true" /> <property name="testOnReturn" value="false" /> <property name="jmxEnabled" value="true" /> <property name="blockWhenExhausted" value="false" /> </bean>
而JedisPoolConfig其實就是繼承自GenericObjectPoolConfig,GenericObjectPoolConfig的相關配置屬性列表如下:
屬性 | 類型 | 默認值 | 作用 |
maxTotal | int | 8 | 池中最多可用的實例個數 |
maxIdle | int | 8 | 池中最多可容納的實例(instances)個數 |
minIdle | int | 0 | 池中最少需要容納的實例(instances)個數 |
lifo | boolean | TRUE | 池中實例的操作是否按照LIFO(後進先出)的原則 |
fairness | boolean | FALSE | 租借池化對象的客戶端按照FIFO進行 |
maxWaitMillis | long | -1 | 調用borrowObject方法時,需要等待的最長時間 |
minEvictableIdleTimeMillis | long | 1800000 | 池中對象處於空閒狀態開始到被回收的最短時間 |
softMinEvictableIdleTimeMillis | long | 3 | 池中對象處於空閒狀態開始到被回收的最短時間 |
numTestsPerEvictionRun | int | 3 |
池中處於空閒狀態的對象每次被檢測是否回收時 最多隻檢測3個處於空閒狀態的對象,比如該值設置爲3,此時池中有5個閒置對象,那麼每次只會檢查前三個閒置對象 |
evictionPolicyClassName | String |
org.apache.commons.pool2. impl.DefaultEvictionPolicy |
回收策略 |
testOnCreate | boolean | FALSE |
調用borrowObject方法時,依據此標識判斷是否 需要對返回的結果進行校驗,如果校驗失敗會刪 除當前實例,並嘗試再次獲取 |
testOnBorrow | boolean | FALSE |
調用borrowObject方法時,依據此標識判斷是否 需要對返回的結果進行校驗,如果校驗失敗會 刪除當前實例,並嘗試再次獲取 |
testOnReturn | boolean | FALSE |
調用returnObject方法時,依據此標識判斷是否 需要對返回的結果進行校驗 |
testWhileIdle | boolean | FALSE | 閒置實例校驗標識,如果校驗失敗會刪除當前實例 |
timeBetweenEvictionRunsMillis | long | -1 | 閒置實例校驗器啓動的時間間隔,單位是毫秒 |
blockWhenExhausted | boolean | TRUE |
當池中對象都被借出後,客戶端來租借對象, 此時是否進行阻塞還是非阻塞,默認阻塞 |
jmxEnabled | boolean | TRUE | 開啓JMX開關 |
jmxNamePrefix | String | pool | JMX前綴 |
jmxNameBase | String | null | JMX根名字 |
- 網友遇到的問題1:mysql服務端設置了連接8小時失效,但是commons-pool2對應的對象池中沒有配置上timeBetweenEvictionRunsMillis minEvictableIdleTimeMillis numTestsPerEvictionRun,導致沒有對池化的mysql客戶端進行檢測,所以經驗是服務器端如果設置了idel>0的空閒時間, 那麼客戶端最好設置上對應的心跳頻率即多久心跳一次;
- 網友遇到的問題2:redis的服務端設置了timeout=0,由於網絡原因,commons-pool2已經將池中redis客戶端銷燬,但是服務端redis因爲配置了timeout=0禁用了關閉限制的redis客戶端功能,導致服務端大量殭屍進程存在,所以經驗是配置redis服務端的timeout爲一個大於0的值,意思是客戶端如果空閒了且空閒時間大於該值,服務端就會關閉該連接
8.Apache commons-pool2使用的基本步驟
- 步驟一:實現自己的PooledObjectFactory
- 步驟二:創建ObjectPool對象
- 步驟三:從ObjectPool獲取到PooledObject對象,進行相關業務操作
8.1 實現自己的PooledObjectFactory
因爲Apache commons-pool2不知道開發者需要被池化的對象,所以沒有提供默認的相關實現,開發者這一步必須做,這裏自己實現Apache commons-pool2的PooledObjectFactory管理StringBuffer爲例講解,其代碼如下:
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
/**
*
* @ClassName: MyPooledObjectFactoryExample
* @Description: 自己實現Apache commons-pool2的PooledObjectFactory管理StringBuffer
* @author aperise
* @date 2017年11月15日 下午9:22:11
*/
public class MyPooledObjectFactoryExample implements PooledObjectFactory<StringBuffer> {
/**
* //創建StringBuffer對象
*/
@Override
public PooledObject<StringBuffer> makeObject() throws Exception {
return new DefaultPooledObject<StringBuffer>(new StringBuffer());
}
/**
* //銷燬StringBuffer對象
*/
@Override
public void destroyObject(PooledObject<StringBuffer> p) throws Exception {
StringBuffer sb = p.getObject();
sb = null;
}
/**
* //校驗StringBuffer對象
*/
@Override
public boolean validateObject(PooledObject<StringBuffer> p) {
return p.getObject() != null;
}
/**
* //激活StringBuffer對象
*/
@Override
public void activateObject(PooledObject<StringBuffer> p) throws Exception {
if (null == p.getObject())
p = new DefaultPooledObject<StringBuffer>(new StringBuffer());
}
/**
* //對話StringBuffer對象,這裏是個空實現
*/
@Override
public void passivateObject(PooledObject<StringBuffer> p) throws Exception {
// TODO Auto-generated method stub
}
}
8.2 創建ObjectPool對象
Apache commons-pool2默認提供了對於ObjectPool的5種實現SoftReferenceObjectPool GenericObjectPool ProxiedObjectPool GenericKeyedObjectPool ProxiedKeyedObjectPool,開發者一般不需要自己實現,直接選擇使用其中一個即可,如果不用默認的實現也可以自己去實現。
//使用Apache commons-pool2的ObjectPool的默認實現GenericObjectPool
ObjectPool op = new GenericObjectPool<StringBuffer>(new MyPooledObjectFactoryExample(),new GenericObjectPoolConfig());
8.3 從ObjectPool獲取到PooledObject對象,進行相關業務操作
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
/**
*
* @ClassName: CommonsPool2Test
* @Description: 自己實現Apache commons-pool2的PooledObjectFactory管理StringBuffer
* @author aperise
* @date 2017年11月15日 下午9:28:28
*/
public class CommonsPool2Test {
public static void main(String[] args) {
//使用Apache commons-pool2的ObjectPool的默認實現GenericObjectPool
ObjectPool op = new GenericObjectPool<StringBuffer>(new MyPooledObjectFactoryExample(),new GenericObjectPoolConfig());
//從ObjectPool租借對象StringBuffer
StringBuffer sb = (StringBuffer) op.borrowObject();
sb.append("aaa");
System.out.println(sb.toString());
//歸還對象StringBuffer
op.returnObject(sb);
}
}