首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

WebLogic 類別載入死循環疑雲:Dom4j/Xerces SAXParserFactory 查找機制深度剖析

     編輯:Java 相關知識

現場症狀

  • WebLogic 不斷載入類別,執行緒大量掛在 ZipFile.getEntry()
  • Thread Dump 顯示堆疊:SAXParserFactory.newInstance() → FactoryFinder.find() → ClassLoader.getResourceAsStream() → JarURLConnection → ZipFile.getEntry()

根因追溯:SAXParserFactory 查找順序

javax.xml.parsers.FactoryFinder.find() 依序嘗試:

  1. System Property: -Djavax.xml.parsers.SAXParserFactory=xxx
  2. JRE 配置檔: $JAVA_HOME/lib/jaxp.properties 指定實作類
  3. JAR 服務提供者機制 (SPI): META-INF/services/javax.xml.parsers.SAXParserFactory — 此步驟需掃描 JAR 檔 (ZipFile.getEntry)
  4. 預設回退值: org.apache.xerces.jaxp.SAXParserFactoryImpl (在當前 ClassLoader 查找)

客戶環境前三條件均不滿足,每次解析 XML 皆走到第 3 步,**重複掃描 xerces.jar 的 META-INF 目錄**,導致大量執行緒在 ZipFile.getEntry() 同步鎖上爭用,形成效能瓶頸。

三大解決方案 (任選一)

  1. JVM 啟動參數指定: -Djavax.xml.parsers.SAXParserFactory=your.impl.Class
  2. 建立 jaxp.properties: 在 $JAVA_HOME/lib/ 建檔,寫入 javax.xml.parsers.SAXParserFactory=your.impl.Class
  3. 展開 JAR 部署: 將 xerces.jar 解壓為類別目錄結構,避免 ZipFile 存取

延伸疑問:自定 ParserFactory 為何未生效?

現象

Thread Dump 顯示:GenericClassLoader.loadClass() → FactoryFinder.newInstance() → find(),大量執行緒阻塞於 WebLogic 類別載入器鎖。

真相解析

ClassLoader.loadClass() 預設實作邏輯:

  1. findLoadedClass() — 檢查已載入快取
  2. 委派父載入器 loadClass()
  3. findClass() — 實際查找定義

堆疊顯示的「大量 loadClass」多半是 **快取查找 (findLoadedClass) 失敗後的父載入器委派**,非真正重複載入類別。WebLogic 階層式 ClassLoader (應用 → 模組 → 系統) 導致委派鏈較長,鎖競爭顯著。

最佳實踐總結

  1. 生產環境必須固定 XML 解析器實作: 透過 System Property 或 jaxp.properties 明確指定,消除 SPI 掃描開銷
  2. 避免多版本 Xerces 共存: 統一由伺服器模組提供,應用層勿重複打包
  3. 監控類別載入鎖競爭: 發現大量 loadClass 阻塞時,優先檢查 SPI 機制與 ClassLoader 階層
  4. 理解 loadClass ≠ 類別載入: 多為快取未命中 + 父委派,非真正定義類別

參考代碼片段 (JDK 內部實作)

// FactoryFinder.find() 關鍵邏輯
static Object find(String factoryId, String fallbackClassName) {
    // 1. System Property
    String systemProp = System.getProperty(factoryId);
    if (systemProp != null) return newInstance(systemProp, classLoader);

    // 2. jaxp.properties
    File configFile = new File(System.getProperty("java.home") + "/lib/jaxp.properties");
    if (configFile.exists()) { /* 載入屬性檔 */ }

    // 3. META-INF/services/ (SPI) — 會開啟 JAR 檔
    String serviceId = "META-INF/services/" + factoryId;
    InputStream is = classLoader.getResourceAsStream(serviceId); // 這裡觸發 ZipFile.getEntry
    if (is != null) { /* 讀取實作類名 */ }

    // 4. Fallback
    return newInstance(fallbackClassName, classLoader);
}

// ClassLoader.loadClass() 標準實作
protected synchronized Class loadClass(String name, boolean resolve) {
    Class c = findLoadedClass(name); // 快取查找
    if (c == null) {
        try {
            if (parent != null) c = parent.loadClass(name, false);
            else c = findBootstrapClassOrNull(name);
        } catch (ClassNotFoundException e) {
            c = findClass(name); // 真正查找
        }
    }
    if (resolve) resolveClass(c);
    return c;
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved