色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

一種優雅解決MySQL驅動中虛引用導致GC耗時較長問題的方法

OSC開源社區 ? 來源:DailyHappy ? 2023-12-20 09:52 ? 次閱讀

背景

在之前文章中寫過 MySQL JDBC 驅動中的虛引用導致 JVM GC 耗時較長的問題,在驅動代碼(mysql-connector-java 5.1.38版本)中 NonRegisteringDriver 類有個虛引用集合 connectionPhantomRefs 用于存儲所有的數據庫連接,NonRegisteringDriver.trackConnection 方法負責把新創建的連接放入集合,虛引用隨著時間積累越來越多,導致 GC 時處理虛引用的耗時較長,影響了服務的吞吐量:

publicConnectionImpl(StringhostToConnectTo,intportToConnectTo,Propertiesinfo,StringdatabaseToConnectTo,Stringurl)throwsSQLException{
...
NonRegisteringDriver.trackConnection(this);
...
}
publicclassNonRegisteringDriverimplementsDriver{
...
protectedstaticfinalConcurrentHashMapconnectionPhantomRefs=newConcurrentHashMap();

protectedstaticvoidtrackConnection(com.mysql.jdbc.ConnectionnewConn){
ConnectionPhantomReferencephantomRef=newConnectionPhantomReference((ConnectionImpl)newConn,refQueue);
connectionPhantomRefs.put(phantomRef,phantomRef);
}
...
}

嘗試減少數據庫連接的生成速度,來降低虛引用的數量,但是效果并不理想。最終的解決方案是通過反射獲取虛引用集合,利用定時任務來定期清理集合,避免 GC 處理虛引用耗時較長。

//每兩小時清理connectionPhantomRefs,減少對mixedGC的影響
SCHEDULED_EXECUTOR.scheduleAtFixedRate(()->{
try{
FieldconnectionPhantomRefs=NonRegisteringDriver.class.getDeclaredField("connectionPhantomRefs");
connectionPhantomRefs.setAccessible(true);
Mapmap=(Map)connectionPhantomRefs.get(NonRegisteringDriver.class);
if(map.size()>50){
map.clear();
}
}catch(Exceptione){
log.error("connectionPhantomRefsclearerror!",e);
}
},2,2,TimeUnit.HOURS);

利用定時任務清理虛引用效果立竿見影,每日幾億請求的服務 mixed GC 耗時只有 10 - 30 毫秒左右,系統也很穩定,線上運行將近一年沒有任何問題。

優化——暴力破解到優雅配置

最近又有同事遇到相同的問題,使用的 mysql-connector-java 版本與我們使用的版本一致,查看最新版本(8.0.32)的代碼發現對數據庫連接的虛引用有新的處理方式,不像老版本(5.1.38)中每一個連接都會生成虛引用,而是可以通過參數來控制是否需要生成。類 AbandonedConnectionCleanupThread 的相關代碼如下:

//靜態變量通過System.getProperty獲取配置
privatestaticbooleanabandonedConnectionCleanupDisabled=Boolean.getBoolean("com.mysql.cj.disableAbandonedConnectionCleanup");

publicstaticbooleangetBoolean(Stringname){
returnparseBoolean(System.getProperty(name));
}

protectedstaticvoidtrackConnection(MysqlConnectionconn,NetworkResourcesio){
//判斷配置的屬性值來決定是否需要生成虛引用
if(!abandonedConnectionCleanupDisabled){
···
ConnectionFinalizerPhantomReferencereference=newConnectionFinalizerPhantomReference(conn,io,referenceQueue);
connectionFinalizerPhantomRefs.add(reference);
···
}
}

mysql-connector-java 的維護者應該是注意到了虛引用對 GC 的影響,所以優化了代碼,讓用戶可以自定義虛引用的生成。

有了這個配置,就可以在啟動參數上設置屬性:

java-jarapp.jar-Dcom.mysql.cj.disableAbandonedConnectionCleanup=true

或者在代碼里設置屬性:

System.setProperty(PropertyDefinitions.SYSP_disableAbandonedConnectionCleanup,"true");

當 com.mysql.cj.disableAbandonedConnectionCleanup=true 時,生成數據庫連接時就不會生成虛引用,對 GC 就沒有任何影響了。

建議還是使用第一種方式,通過啟動參數配置更靈活一點。

什么是虛引用

有些讀者看到這里知道 mysql-connector-java 生成的虛引用對 GC 有一些副作用,但是還不太了解虛引用到底是什么,有什么作用,這里我們在虛引用上做一點點拓展。

Java 虛引用(Phantom Reference)是Java中一種特殊的引用類型,它是最弱的一種引用。與其他引用不同,虛引用并不會影響對象的生命周期,也不會影響對象的垃圾回收。虛引用主要用于在對象被回收時收到系統通知,以便在回收時執行一些必要的清理工作。

上述虛引用的定義還是比較難理解,我們用代碼來輔助理解:

先來生成一個虛引用:

//虛引用隊列
ReferenceQueuequeue=newReferenceQueue<>();
//關聯對象
Objecto=newObject();
//調用構造方法生成一個虛引用第一個參數就是關聯對象第二個參數是關聯隊列
PhantomReferencephantomReference=newPhantomReference<>(o,queue);
//執行垃圾回收
System.gc();
//延時確保回收完畢
Thread.sleep(100L);
//當Objecto被回收時可以從虛引用隊列里獲取到與之關聯的虛引用這里就是phantomReference這個對象
Referencepoll=queue.poll();

虛引用的構造方法需要兩個入參,第一個就是關聯的對象、第二個是虛引用隊列 ReferenceQueue。虛引用需要和 ReferenceQueue 配合使用,當對象 Object o 被垃圾回收時,與 Object o 關聯的虛引用就會被放入到 ReferenceQueue 中。通過從 ReferenceQueue 中是否存在虛引用來判斷對象是否被回收。

我們再來理解上面對虛引用的定義,虛引用不會影響對象的生命周期,也不會影響對象的垃圾回收。如果上述代碼里的phantomReference 是一個普通的對象,那么在執行 System.gc() 時 Object o 一定不會被回收掉,因為普通對象持有 Object o 的強引用,還不會被作為垃圾。這里的 phantomReference 是一個虛引用的話 Object o 就會被直接回收掉。然后會將關聯的虛引用放到隊列里,這就是虛引用關聯對象被回收時會收到系統通知的機制。

一些實踐能力很強的讀者會復制上述代碼去運行,發現垃圾回收之后隊列里并沒有虛引用。這是因為 Object o 還在棧里,屬于是 GC Root 的一種,不會被垃圾回收。我們可以這樣改寫:

staticReferenceQueuequeue=newReferenceQueue<>();

publicstaticvoidmain(String[]args)throwsInterruptedException{
PhantomReferencephantomReference=buildReference();
System.gc();Thread.sleep(100);
System.out.println(queue.poll());
}

publicstaticPhantomReferencebuildReference(){
Objecto=newObject();
returnnewPhantomReference<>(o,queue);
}

不在 main 方法里實例化關聯對象 Object o,而是利用一個 buildReference 方法來實例化,這樣在執行垃圾回收的時候,Object o 已經出棧了,不再是 GC Root,會被當做垃圾來回收。這樣就能從虛引用隊列里取出關聯的虛引用進行后續處理。

關聯對象真的被回收了嗎

執行完垃圾回收之后,我們確實能從虛引用隊列里獲取到虛引用了,我們可以思考一下,與該虛引用關聯的對象真的已經被回收了嗎?

使用一個小實驗來探索答案:

publicstaticvoidmain(String[]args){
ReferenceQueuequeue=newReferenceQueue<>();
PhantomReferencephantomReference=newPhantomReference<>(
newbyte[1024*1024*2],queue);
System.gc();Thread.sleep(100L);
System.out.println(queue.poll());
byte[]bytes=newbyte[1024*1024*4];
}

代碼里生成一個虛引用,關聯對象是一個大小為 2M 的數組,執行垃圾回收之后嘗試再實例化一個大小為 4M 的數組。如果我們從虛引用隊列里獲取到虛引用的時候關聯對象已經被回收,那么就能正常申請到 4M 的數組。(設置堆內存大小為 5M -Xmx5m -Xms5m)

執行代碼輸出如下:

java.lang.ref.PhantomReference@533ddba
Exceptioninthread"main"java.lang.OutOfMemoryError:Javaheapspace
atcom.ppphuang.demo.phantomReference.PhantomReferenceDemo.main(PhantomReferenceDemo.java:15)

從輸出可以看到,申請 4M 內存的時候內存溢出,那么問題的答案就很明顯了,關聯對象并沒有被真正的回收,內存也沒有被釋放。

再做一點小小的改造,實例化新數組的之前將虛引用直接置為 null,這樣關聯對象就能被真正的回收掉,也能申請足夠的內存:

publicstaticvoidmain(String[]args){
ReferenceQueuequeue=newReferenceQueue<>();
PhantomReferencephantomReference=newPhantomReference<>(
newbyte[1024*1024*2],queue);
System.gc();Thread.sleep(100L);
System.out.println(queue.poll());
//虛引用直接置為null
phantomReference=null;
byte[]bytes=newbyte[1024*1024*4];
}

如果我們使用了虛引用,但是沒有及時清理虛引用的話可能會導致內存泄露

虛引用的使用場景——mysql-connector-java 虛引用源碼分析

讀到這里相信你已經了解了虛引用的一些基本情況,那么它的使用場景在哪里呢?

最典型的場景就是最開始寫到的 mysql-connector-java 里處理 MySQL 連接的兜底邏輯。用虛引用來包裝 MySQL 連接,如果一個連接對象被回收的時候,會從虛引用隊列里收到通知,如果有些連接沒有被正確關閉的話,就會在回收之前進行連接關閉的操作。

從 mysql-connector-java 的 AbandonedConnectionCleanupThread 類代碼中可以發現并沒有使用原生的 PhantomReference 對象,而是使用的是包裝過的 ConnectionFinalizerPhantomReference,增加了一個屬性 NetworkResources,這是為了方便從虛引用隊列中的虛引用上獲取到需要處理的資源。包裝類中還有一個 finalizeResources 方法,用來關閉網絡連接:

privatestaticclassConnectionFinalizerPhantomReferenceextendsPhantomReference{
//放置需要GC后后置處理的網絡資源
privateNetworkResourcesnetworkResources;
ConnectionFinalizerPhantomReference(MysqlConnectionconn,NetworkResourcesnetworkResources,ReferenceQueuerefQueue){
super(conn,refQueue);
this.networkResources=networkResources;
}
voidfinalizeResources(){
if(this.networkResources!=null){
try{
this.networkResources.forceClose();
}finally{
this.networkResources=null;
}
}
}
}

AbandonedConnectionCleanupThread 實現了 Runnable 接口,在 run 方法里循環讀取虛引用隊列 referenceQueue 里的虛引用,然后調用 finalizeResource 方法來進行后置的處理,避免連接泄露:

publicvoidrun(){
while(true){
try{
...
Referencereference=referenceQueue.remove(5000L);
if(reference!=null){
//強轉為ConnectionFinalizerPhantomReference
finalizeResource((ConnectionFinalizerPhantomReference)reference);
}
...
}
}
}

privatestaticvoidfinalizeResource(ConnectionFinalizerPhantomReferencereference){
try{
//兜底處理網絡資源
reference.finalizeResources();
reference.clear();
}finally{
//移除虛引用避免可能造成的內存溢出
connectionFinalizerPhantomRefs.remove(reference);
}
}

如果你希望在某些對象被回收的時候做一些后置工作,可以參考 mysql-connector-java 中的一些實現邏輯。

總結

本文簡述了一種優雅解決 MySQL 驅動中虛引用導致 GC 耗時較長問題的解決方法、也根據自己的理解講述了虛引用的作用、結合 MySQL 驅動的源碼描述了虛引用的使用場景,希望對你能有所幫助。






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2980

    瀏覽量

    105715
  • MySQL
    +關注

    關注

    1

    文章

    836

    瀏覽量

    26945
  • JVM
    JVM
    +關注

    關注

    0

    文章

    159

    瀏覽量

    12341

原文標題:MySQL驅動中虛引用GC耗時優化與源碼分析

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    步進電機5驅動方法的利弊分析

    這5驅動方法的利弊,以選擇合適的驅動方法來做電機驅動。1. 恒電壓
    發表于 01-27 14:45

    C++服務編譯耗時優化的原理和服務分析

    編譯的耗時。通過這個方式能夠找到各個文件編譯耗時的共性,下圖是編譯展開后文件大小截圖。3.2 頭文件依賴分析頭文件依賴分析是從引用頭文件數量的角度來看代碼是否合理的一種分析方式,我們實
    發表于 12-23 17:32

    一種優雅的方式去實現個Verilog版的狀態機

    描述:基于此,我們便可以方便快捷的去描述狀態機,以一種優雅的方式去實現狀態機描述,而對于他人閱讀來講也是相當OK的。等等,還有更好玩兒的。在SpinalHDL里,定義了四可以聲明狀態的類型
    發表于 07-13 14:56

    什么是PCBA焊?解決PCBA焊的方法介紹

    層。它們沒有完全接觸在起。肉眼般無法看出其狀態。 但是其電氣特性并沒有導通或導通不良。影響電路特性。  PCBA焊是常見的一種線路故障,有兩
    發表于 04-06 16:25

    一種有效的視頻序列拼接方法

    針對視頻序列拼接容易造成拼接耗時較長、拼接效果不佳等問題,提出一種有效的視頻序列拼接方法,首先,利用時域檢測窗口對視頻序列進行關鍵幀的提取
    發表于 09-03 16:24 ?30次下載

    一種帶有均衡策略的無通道容錯路由算法

    一種帶有均衡策略的無通道容錯路由算法
    發表于 01-07 20:49 ?0次下載

    DSP硬件驅動程序的一種方法

    DSP硬件驅動程序的一種方法
    發表于 10-19 10:48 ?1次下載
    DSP硬件<b class='flag-5'>驅動</b>程序的<b class='flag-5'>一種方法</b>

    "引用"在Android和Java的工作原理

    本文講的是徹底理解引用在Android和Java的工作原理,引用指向了個對象,你能通過引用訪問對象。Java默認有4
    發表于 11-27 08:55 ?1322次閱讀
    "<b class='flag-5'>引用</b>"在Android和Java<b class='flag-5'>中</b>的工作原理

    基于shared_ptr的C++非侵入式引用計數解決方案的缺陷

    嚴格地說,引用計數其實也是一種最樸素的GC。相對于現代的GC技術,引用計數的實現簡單,但相應地,它也存在著循環
    的頭像 發表于 06-13 14:24 ?1476次閱讀

    一種AUTOSAR軟件架構RTE的實現方法

    介紹了一種AUTOSAR軟件架構RTE的實現方法
    發表于 07-13 16:02 ?7次下載

    次JVM GC長暫停的排查過程

    在高并發下,Java 程序的 GC 問題屬于很典型的類問題,帶來的影響往往會被進步放大。不管是「GC 頻率過快」還是「GC
    的頭像 發表于 01-17 10:08 ?729次閱讀

    運放電路為什么會出現短和斷?

    運放電路為什么會出現短和斷?? 運放電路是電子電路中常用的一種放大電路,可以實現信號放大和信號濾波等功能。然而在實際應用,經常會出現
    的頭像 發表于 09-20 16:29 ?5111次閱讀

    php的mysql無法啟動

    MySQL一種常用的關系型數據庫管理系統,而PHP是一種廣泛應用于服務器端的腳本語言。在使用PHP開發網站或應用時,經常會碰到MySQL無法啟動的問題。本文將詳細介紹解決
    的頭像 發表于 12-04 15:59 ?1769次閱讀

    導致MySQL索引失效的情況以及相應的解決方法

    導致MySQL索引失效的情況以及相應的解決方法? MySQL索引的目的是提高查詢效率,但有些情況下索引可能會失效,導致查詢變慢或效果不如預期
    的頭像 發表于 12-28 10:01 ?877次閱讀

    柵極驅動ic焊會燒嗎

    柵極驅動IC焊是否會導致燒毀,這個問題涉及到多個因素,包括焊的嚴重程度、工作環境條件以及柵極驅動IC本身的特性等。以下是對這
    的頭像 發表于 09-18 09:26 ?503次閱讀
    主站蜘蛛池模板: 欧美一区二区日韩一区二区 | 精品午夜久久影视 | 無码一区中文字幕少妇熟女网站 | 国产午夜一级鲁丝片 | 日本19xxxx撤尿 | 在线观看成年人免费视频 | 99视频偷窥在线精品国自产拍 | 久久91精品国产91 | 国产真实强被迫伦姧女在线观看 | 国产色精品久久人妻99蜜桃麻豆 | 国产偷国产偷亚洲高清人乐享 | 国精产品一区一区三区有限在线 | 俄罗斯女人Z0ZOZO | 午夜国产视频 | 国产精品热久久高潮AV袁孑怡 | 女人会操出水图 | 日本久久精品视频 | 黑人干日本美女 | 国内精品自线在拍2020不卡 | 人人爽久久久噜噜噜丁香AV | 肉蒲团从国内封禁到日本成经典 | 无码一卡二卡三卡四卡 | 美女张开腿露尿口给男人亲 | 99在线播放| 国产二区自拍 | 久久观看视频 | 亚欧免费观看在线观看更新 | 欧美日韩看看2015永久免费 | 无码天堂亚洲内射精品课堂 | 国产一区亚洲 | 国产在线高清亚洲精品一区 | 黄色三级在线 | 2018久久视频在线视频观看 | 日日碰狠狠添天天爽 | 国产精品一区二区三区免费 | 国产精品人妻午夜福利 | 乡村教师电影完整版在线观看 | 一品探花论坛 | 最近日本MV字幕免费观看视频 | 97在线视频免费播放 | 国产成人在线观看免费网站 |