最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL?驅(qū)動中虛引用?GC?耗時優(yōu)化與源碼分析

 更新時間:2023年05月16日 08:44:58   作者:PPPHUANG  
這篇文章主要為大家介紹了MySQL?驅(qū)動中虛引用?GC?耗時優(yōu)化與源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

本文要點:

  • 一種優(yōu)雅解決 MySQL 驅(qū)動中虛引用導(dǎo)致 GC 耗時較長問題的解決方法
  • 虛引用的作用與使用場景
  • MySQL 驅(qū)動源碼中的虛引用分析

背景

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

public ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
    ...
    NonRegisteringDriver.trackConnection(this);
  ...
}
public class NonRegisteringDriver implements Driver {
  ...
 ?protected static final ConcurrentHashMap<ConnectionPhantomReference, ConnectionPhantomReference> connectionPhantomRefs = new ConcurrentHashMap();
  protected static void trackConnection(com.mysql.jdbc.Connection newConn) {
 ? ? ? ?ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl)newConn, refQueue);
 ? ? ? ?connectionPhantomRefs.put(phantomRef, phantomRef);
 ?  }
  ...
}

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

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

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

優(yōu)化——由暴力破解到優(yōu)雅配置

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

//靜態(tài)變量通過 System.getProperty 獲取配置
private static boolean abandonedConnectionCleanupDisabled = Boolean.getBoolean("com.mysql.cj.disableAbandonedConnectionCleanup");
public static boolean getBoolean(String name) {
      return parseBoolean(System.getProperty(name));
}
protected static void trackConnection(MysqlConnection conn, NetworkResources io) {
          //判斷配置的屬性值來決定是否需要生成虛引用
      if (!abandonedConnectionCleanupDisabled) {
         ···
          ConnectionFinalizerPhantomReference reference = new ConnectionFinalizerPhantomReference(conn, io, referenceQueue);
          connectionFinalizerPhantomRefs.add(reference);
         ··· 
      }
  }

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

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

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

或者在代碼里設(shè)置屬性:

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

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

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

什么是虛引用

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

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

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

先來生成一個虛引用:

//虛引用隊列
ReferenceQueue<Object> queue = new ReferenceQueue<>();
//關(guān)聯(lián)對象
Object o = new Object();
//調(diào)用構(gòu)造方法生成一個虛引用 第一個參數(shù)就是關(guān)聯(lián)對象 第二個參數(shù)是關(guān)聯(lián)隊列
PhantomReference<Object> phantomReference = new PhantomReference<>(o, queue);
//執(zhí)行垃圾回收
System.gc();
//延時確?;厥胀戤?
Thread.sleep(300L);
//當(dāng) Object o 被回收時可以從虛引用隊列里獲取到與之關(guān)聯(lián)的虛引用 這里就是 phantomReference 這個對象
Reference<?> poll = queue.poll();

虛引用的構(gòu)造方法需要兩個入?yún)ⅲ谝粋€就是關(guān)聯(lián)的對象、第二個是虛引用隊列 ReferenceQueue。虛引用需要和 ReferenceQueue 配合使用,當(dāng)對象 Object o 被垃圾回收時,與 Object o 關(guān)聯(lián)的虛引用就會被放入到 ReferenceQueue 中。通過從 ReferenceQueue 中是否存在虛引用來判斷對象是否被回收。

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

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

static ReferenceQueue<Object> queue = new ReferenceQueue<>();
public static void main(String[] args) throws InterruptedException {
    PhantomReference<Object> phantomReference = buildReference();
    System.gc();Thread.sleep(100);
    System.out.println(queue.poll());
}
public static PhantomReference<Object> buildReference() {
    Object o = new Object();
    return new PhantomReference<>(o, queue);
}

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

關(guān)聯(lián)對象真的被回收了嗎

執(zhí)行完垃圾回收之后,我們確實能從虛引用隊列里獲取到虛引用了,我們可以思考一下,與該虛引用關(guān)聯(lián)的對象真的已經(jīng)被回收了嗎?

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

public static void main(String[] args) {
      ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
      PhantomReference<byte[]> phantomReference = new PhantomReference<>(
              new byte[1024 * 1024 * 2], queue);
      System.gc();Thread.sleep(100L);
      System.out.println(queue.poll());
      byte[] bytes = new byte[1024 * 1024 * 4];
  }

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

執(zhí)行代碼輸出如下:

java.lang.ref.PhantomReference@533ddba
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.ppphuang.demo.phantomReference.PhantomReferenceDemo.main(PhantomReferenceDemo.java:15)

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

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

public static void main(String[] args) {
      ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
      PhantomReference<byte[]> phantomReference = new PhantomReference<>(
              new byte[1024 * 1024 * 2], queue);
      System.gc();Thread.sleep(100L);
      System.out.println(queue.poll());
          //虛引用直接置為 null
          phantomReference = null;
      byte[] bytes = new byte[1024 * 1024 * 4];
  }

 如果我們使用了虛引用,但是沒有及時清理虛引用的話可能會導(dǎo)致內(nèi)存泄露。

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

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

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

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

private static class ConnectionFinalizerPhantomReference extends PhantomReference<MysqlConnection> {
      //放置需要GC后后置處理的網(wǎng)絡(luò)資源
      private NetworkResources networkResources;
      ConnectionFinalizerPhantomReference(MysqlConnection conn, NetworkResources networkResources, ReferenceQueue<? super MysqlConnection> refQueue) {
          super(conn, refQueue);
          this.networkResources = networkResources;
      }
      void finalizeResources() {
          if (this.networkResources != null) {
              try {
                  this.networkResources.forceClose();
              } finally {
                  this.networkResources = null;
              }
          }
      }
  }

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

public void run() {
    while(true) {
        try {
              ...
            Reference<? extends MysqlConnection> reference = referenceQueue.remove(5000L);
            if (reference != null) {
                  //強轉(zhuǎn)為 ConnectionFinalizerPhantomReference
                finalizeResource((ConnectionFinalizerPhantomReference)reference);
            }
              ...
        }
    }
}
private static void finalizeResource(ConnectionFinalizerPhantomReference reference) {
    try {
          //兜底處理網(wǎng)絡(luò)資源
        reference.finalizeResources();
        reference.clear();
    } finally {
          //移除虛引用 避免可能造成的內(nèi)存溢出
        connectionFinalizerPhantomRefs.remove(reference);
    }
}

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

總結(jié)

本文簡述了一種優(yōu)雅解決 MySQL 驅(qū)動中虛引用導(dǎo)致 GC 耗時較長問題的解決方法、也根據(jù)自己的理解講述了虛引用的作用、結(jié)合 MySQL 驅(qū)動的源碼描述了虛引用的使用場景,希望對你能有所幫助,更多關(guān)于MySQL 驅(qū)動中虛引用 GC 耗時優(yōu)化與源碼分析的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • mysql如何變更自增主鍵id起始值

    mysql如何變更自增主鍵id起始值

    這篇文章主要介紹了mysql如何變更自增主鍵id起始值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Mysql my.ini 配置文件詳解

    Mysql my.ini 配置文件詳解

    Mysql my.ini 配置文件詳解
    2009-06-06
  • MySQL修改時區(qū)的方法圖文詳解

    MySQL修改時區(qū)的方法圖文詳解

    這篇文章主要給大家介紹了關(guān)于MySQL修改時區(qū)的相關(guān)資料,時區(qū)是MySQL中非常重要的概念,正確設(shè)置時區(qū)可以確保應(yīng)用程序工作正常并避免時間錯誤,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-09-09
  • 深入理解r2dbc在mysql中的使用

    深入理解r2dbc在mysql中的使用

    這篇文章主要介紹了深入理解r2dbc在mysql中的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • MySQL根據(jù)某一個或者多個字段查找重復(fù)數(shù)據(jù)的sql語句

    MySQL根據(jù)某一個或者多個字段查找重復(fù)數(shù)據(jù)的sql語句

    這篇文章主要介紹了MySQL根據(jù)某一個或者多個字段查找重復(fù)數(shù)據(jù)的sql語句,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-12-12
  • ?MySQL中?between and的基本用法?操作方法

    ?MySQL中?between and的基本用法?操作方法

    本文主要介紹了MySQL中BETWEEN AND操作符的基本用法,包括數(shù)值查詢和時間范圍查詢,同時還詳細解釋了NOT BETWEEN AND的使用方法,并通過實例進行了詳細的演示,其中,BETWEEN AND可以用于數(shù)值、日期等類型的字段,包括邊界值
    2024-10-10
  • mysql如何設(shè)置不區(qū)分大小寫

    mysql如何設(shè)置不區(qū)分大小寫

    在本篇文章里小編給大家整理了關(guān)于mysql設(shè)置不區(qū)分大小寫的方法,需要的朋友們跟著學(xué)習(xí)下。
    2020-06-06
  • MySql開發(fā)之自動同步表結(jié)構(gòu)

    MySql開發(fā)之自動同步表結(jié)構(gòu)

    這篇文章主要給大家介紹了關(guān)于MySql開發(fā)之自動同步表結(jié)構(gòu)的相關(guān)資料,這樣可以避免在開發(fā)中由于修改數(shù)據(jù)庫字段導(dǎo)致的數(shù)據(jù)庫表不一致問題,需要的朋友可以參考下
    2021-05-05
  • mysql修改sql_mode報錯的解決

    mysql修改sql_mode報錯的解決

    今天在Navicat中運行sql語句創(chuàng)建數(shù)據(jù)表出現(xiàn)了錯誤Err 1067。本文主要介紹了mysql修改sql_mode報錯的解決,感興趣的可以了解一下
    2021-09-09
  • mysql?sock?文件解析及作用講解

    mysql?sock?文件解析及作用講解

    這篇文章主要為大家介紹了mysql.sock?文件解析及作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07

最新評論

临高县| 新余市| 永新县| 车险| 舞钢市| 铜陵市| 新宾| 绥中县| 阿坝县| 萨迦县| 商城县| 东安县| 凌海市| 兴义市| 上林县| 临沧市| 班戈县| 隆德县| 邯郸市| 玉龙| 大庆市| 牙克石市| 花莲市| 察雅县| 怀安县| 东方市| 延川县| 蓝山县| 中超| 道孚县| 西吉县| 都安| 宁明县| 内丘县| 亳州市| 黔西县| 游戏| 公主岭市| 平塘县| 望谟县| 明溪县|