轉載請標明連接:http://www.cnblogs.com/wingsless/p/6349434.htmlhtml
boneCP本身實現了標準的java.sql.Connection接口,除了會持有Connection對象以外,還會擁有一些屬性用於標記鏈接的建立時間,空閒時間等。java
比較重要的時間概念代碼以下:mysql
if (!recreating){ //上次使用時間戳 connectionLastUsedInMs = System.currentTimeMillis(); //上次重置時間戳 connectionLastResetInMs = System.currentTimeMillis(); //鏈接建立時間 connectionCreationTimeInMs = System.currentTimeMillis(); }
MySQL對鏈接有最大空閒時間的限制,默認是8小時,所以鏈接池在將鏈接分配給客戶端時,應該保證鏈接的可用性。sql
通常會有兩種作法:分配時測試和定時測試。數據庫
分配時測試:在收到客戶端請求時,鏈接池首先對向數據庫發送一條簡單的SQL,判斷鏈接是否可用。less
定時測試:啓動一個測試線程(ConnectionTesterThread),定時每隔一段時間向數據庫發送命令,判斷鏈接是否可用。測試
boneCP採用定時測試的方式保證鏈接的可用。爲了實現該方式,boneCP規定了兩個重要的參數:idleConnectionTestPeriodInSeconds(默認4小時)和idleMaxAgeInSeconds(默認1小時),分別表示空閒鏈接探測週期和鏈接最大可空閒時間。this
默認狀況下,boneCP啓動的keepalive線程每一個1小時會啓動一次,用於檢查鏈接是否達到了空閒時間的上限:線程
代碼片斷1:code
if (connection.isPossiblyBroken() || ((this.idleMaxAgeInMs > 0) && ( System.currentTimeMillis()-connection.getConnectionLastUsedInMs() > this.idleMaxAgeInMs))){ // kill off this connection - it's broken or it has been idle for too long closeConnection(connection); continue; }
若是一個線程距離上次使用已通過去了1小時以上,則會在這段邏輯中被close掉,而後繼續循環掃描其餘的鏈接。
一個鏈接被close掉以後,boneCP會有其餘的線程負責新建鏈接。所以表如今MySQL客戶端上,能夠看到每隔1小時,就會關閉一些鏈接並出現一些新的鏈接(極端狀況下,全部的鏈接都被關閉,並一次性重建全部鏈接)。注意新建鏈接的MySQL分配id和舊鏈接徹底不一樣。
須要注意的是,在默認狀況下,並無觀察到邏輯執行到這裏的現象:
代碼片斷2:
if (this.idleConnectionTestPeriodInMs > 0 && (currentTimeInMs-connection.getConnectionLastUsedInMs() > this.idleConnectionTestPeriodInMs) && (currentTimeInMs-connection.getConnectionLastResetInMs() >= this.idleConnectionTestPeriodInMs)) { // send a keep-alive, close off connection if we fail. if (!this.pool.isConnectionHandleAlive(connection)){ closeConnection(connection); continue; } // calculate the next time to wake up tmp = this.idleConnectionTestPeriodInMs; if (this.idleMaxAgeInMs > 0){ // wake up earlier for the idleMaxAge test? tmp = Math.min(tmp, this.idleMaxAgeInMs); } }
這段邏輯主要判斷是否有鏈接的上一次重置時間距如今超過4小時,若是有,則向MySQL發一個探測命令,而且將鏈接的最後一次重置時間設爲當前時間,若是鏈接alive,返回true,不對鏈接進行close操做。
上一段代碼也是ConnectionTesterThread的邏輯,推斷應該是由於每隔1小時,鏈接就會被關閉重建一次,所以不會存在知足這段邏輯條件的鏈接存在。
若是修改默認值,將idleConnectionTestPeriodInSeconds和idleMaxAgeInSeconds的值對調,那麼boneCP仍會每隔1小時(即idleConnectionTestPeriodInSeconds時間)定時調度keepalive線程。
此時能夠發現上述兩段邏輯都會被執行,每次執行的時候,都會首先執行代碼片斷2中的邏輯,所以每次都會更新ConnectionHandler的最後一次重置時間,可是鏈接仍然不會生存超過4小時,每4小時,邏輯就會進入代碼片斷1中,將鏈接close掉。
如今發現系統運行一段時間之後就會出現fullGC,從內存分析上看,大部份內存都被com.mysql.jdbc.NonRegistingDriver佔去。經過跟蹤jdbc代碼發現,當connection創建的時候,jdbc總會將該connection交給NonRegistingDriver,創建一個虛引用,並將該虛引用放在一個ConcurrentHashMap中。
代碼片斷3:
protected static void trackConnection(Connection newConn) { ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl) newConn, refQueue); connectionPhantomRefs.put(phantomRef, phantomRef); }
內存分析中發現不少內存正是被NonRegistingDriver中的ConcurrentHashMap佔去,所以能夠推斷,應該是新建了大量的Connection致使了大量的NonRegistingDriver對象被新建,從而引起了內存問題。
綜合上面對boneCP的分析,應該是boneCP定時的將鏈接close掉再重建致使的,若是在不是很繁忙的系統上,該狀況應該會比較嚴重。
在沒有設置探測SQL的狀況下,boneCP利用jdbc的getMetaData方法,獲取connection的元數據,從其Javadoc上看,元數據應該包括了數據庫的表,SQL語法,存儲過程等等信息:
The metadata includes information about the database's tables, its supported SQL grammar, its stored procedures, the capabilities of this connection, and so on.
通過抓包分析,實際上getMetaData方法向MySQL 發送了一條簡單的show tables命令,若是收到response則認爲鏈接是alive的。