Apache commons-pool2-2.4.2原始碼學習筆記
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);
}
}