熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

類裝入問題: 類裝入和調試工具介紹

2022-06-13   來源: Java核心技術 

  類裝入器負責把類裝入 Java 虛擬機(JVM)簡單的應用程序可以用 Java 平台內置的類裝入工具裝入類更復雜的應用程序則傾向於定義自己定制的類裝入器但是不論使用哪種類裝入器在類裝入過程中都可能發生許多問題如果想避免這類問題需要理解類裝入的基本機制當問題發生時對於可用的診斷特性和調試技術的了解會有助於解決問題

  在這個系列的文章中我們將深入研究類裝入的問題並用豐富的示例演示它們這份介紹性的文章的第一節描述類裝入的基礎第二節介紹一些 JVM 調試特性系列中剩下的三篇文章將側重於解決類裝入異常並演示一些可能會碰到的更復雜的類裝入問題

  類裝入基礎

  這一節描述類裝入的核心概念為系列剩下的部分提供知識基礎

  類裝入器委托

  類裝入器委托模型 是把裝入請求相互傳給對方的類裝入器圖引導 類裝入器是這個圖的根用單一委托父類 創建類裝入器並在以下位置尋找類


緩存(Cache)
父類(Parent)
自己(Self)

  類裝入器首先判斷要求它裝入的類是否與過去裝入的類相同如果相同就返回上次返回的類(即保存在緩存中的類)如果不是就把裝入類的機會交給父類這兩步遞歸地以深度優先的方式重復如果父類返回 null(或拋出 ClassNotFoundException那麼類裝入器會在自己的路徑中尋找類的源

  因為父類類裝入器總是先得到裝入類的機會所以類裝入器裝入的類最靠近根這意味著所有核心引導類都是由引導裝入器裝入的這就保證裝入了類(例如 javalangObject)的正確版本這也可以讓類裝入器看到自己或父類或祖先裝入的類但是不能看到子女裝入的類

  圖 顯示了三個標准的類裝入器

   類裝入器委托模型
類裝入器委托模型

  與其他類裝入器不同引導類裝入器(也稱作基本(primordial) 類裝入器)不能由 Java 代碼實例化(通常是因為它是作為 VM 本身的一部分實現的)這個類裝入器可以從啟動的類路徑裝入核心系統類通常是位於 jre/lib 目錄的 JAR 文件但是不能用 Xbootclasspath 命令行選項修改這個類路徑(稍後介紹)

  擴展(extension) 類裝入器(也稱作標准擴展 類裝入器)是引導類裝入器的一個孩子它的主要職責是從擴展目錄裝入類通常位於 jre/lib/ext 目錄這提供了簡單地訪問新擴展的能力例如不同的安全擴展不需要修改用戶的類路徑即可實現

  系統(system) 類裝入器(也稱作應用程序 類裝入器)負責從 CLASSPATH 環境變量指定的路徑裝入代碼默認情況下這個類裝入器是用戶創建的任何類裝入器的父類這也是 ClassLoadergetSystemClassLoader() 方法返回的類裝入器

  類路徑選項

  表 總結了設置三個標准類裝入器的類路徑的命令行選項
命令行選項  解釋 涉及的類裝入器   Xbootclasspath:<用 ; 或 : 分隔的目錄和 zip/JAR 文件>     設置引導類和資源的搜索路徑  引導  Xbootclasspath/a:<用 ; 或 : 分隔的目錄和 zip/JAR 文件>    把路徑添加到啟動類路徑的末尾
 引導  Xbootclasspath/p:<用 ; 或 : 分隔的目錄和 zip/JAR 文件>    把路徑添加到啟動類路徑的前面  引導  Dibmjvmbootclasspath=<用 ; 或 : 分隔的目錄和 zip/JAR 文件>    這個屬性的值被用作額外的搜索路徑它被插到 Xbootclasspath/p: 定義的值和啟動類路徑之間啟動類路徑或者是默認值或者是 Xbootclasspath: 選項定義的值  引導  Djavaextdirs=<用 ; 或 : 分隔的目錄和 zip/JAR 文件>    指定擴展類和資源的搜索路徑  擴展
 cp or classpath <用 ; 或 : 分隔的目錄和 zip/JAR 文件>    設置應用程序類和資源的搜索路徑  系統
 Djavaclasspath=<用 ; 或 : 分隔的目錄和 zip/JAR 文件>     設置應用程序類和資源的搜索路徑  系統

類裝入的階段

  類的裝入實際上可以分成三個階段裝入鏈接和初始化

  雖然不是所有的問題但至少大多數與類裝入有關的問題都可以追溯到在這三個階段中發生的某個問題所以對於每一階段的深入理解有助於對類裝入問題的診斷 顯示了這三個階段

   類裝入的階段
類裝入的階段

  裝入 階段包括找到必要的類(通過查找每個類路徑)並裝入字節碼在 JVM 中裝入階段為類對象提供了非常基本的內存結構在這一階段不處理方法字段和引用的其他類所以類還不能使用

  鏈接 是三個階段中最復雜的一個可以把它分成三個主要階段


字節碼驗證 類裝入器對於類的字節碼要做許多檢測以確保格式正確行為正確
類准備 這個階段准備代表每個類中定義的字段方法和實現接口所必需的數據結構
解析 在這個階段類裝入器裝入類所引用的其他所有類可以用許多方式引用類
超類
接口
字段
方法簽名
方法中使用的本地變量

  在初始化 階段類中包含的靜態初始化器都被執行在這一階段末尾靜態字段被初始化成默認值

  在這三個階段末尾類被完整地裝入可以使用了請注意可以用惰性方式執行類裝入所以類裝入過程的某些部分可能在第一次使用類的時候才執行而不是在裝入時執行

  顯式裝入與隱式裝入

  類裝入的方式有兩種 —— 顯式 或 隱式兩者之間有些細微差異

  顯式 類裝入發生在使用以下方法調用裝入的類的時候


clloadClass()(cl 是 javalangClassLoader 的實例)
ClassforName()(啟動的類裝入器是當前類定義的類裝入器)

  當調用其中一個方法的時候指定的類(以類名為參數)由類裝入器裝入如果類已經裝入那麼只是返回一個引用否則裝入器會通過委托模型裝入類

  隱式 類裝入發生在由於引用實例化或繼承導致裝入類的時候(不是通過顯式方法調用)在每種情況下裝入都是在幕後啟動的JVM 會解析必要的引用並裝入類與顯式類裝入一樣如果類已經裝入了那麼只是返回一個引用否則裝入器會通過委托模型裝入類

  類的裝入通常組合了顯式和隱式類裝入例如類裝入器可能先顯式地裝入一個類然後再隱式地裝入它引用的所有類

  JVM 的調試特性

  前面一節介紹了類裝入的基本原則這一節介紹 IBM JVM 中內置的幫助調試的特性其他 JVM 也有類似的調試特性請參閱相關文檔來了解細節

  詳細輸出

  可以用 verbose 命令行選項打開 IBM JVM 的詳細輸出當某些事件發生的時候(例如類裝入時)詳細輸出會在控制台上顯示信息要想得到額外的類裝入信息可以用詳細類輸出可以用 verbose:class 選項啟動這個模式

  解釋詳細輸出

  詳細輸出列出已經打開的所有 JAR 文件包括到這些 JAR 的完整路徑下面是一個示例

[Opened D:\jre\lib\corejar in ms]
[Opened D:\jre\lib\graphicsjar in ms]

  所有裝入的類都已經列出同時還指出它們是從哪個 JAR 文件或目錄裝入的例如

[Loaded javalangNoClassDefFoundError from D:\jre\lib\corejar]
[Loaded javalangClass from D:\jre\lib\corejar]
[Loaded javalangObject from D:\jre\lib\corejar]

  詳細類輸出顯示額外信息例如在裝入超類的時候在運行靜態初始化器的時候下面是一些示例輸出

[Loaded HelloWorld from file:/C:/myclasses/]
[Loading superclass and interfaces of HelloWorld]
[Loaded HelloWorldInterface from file:/C:/myclasses/]
[Loading superclass and interfaces of HelloWorldInterface]
[Preparing HelloWorldInterface]
[Preparing HelloWorld]
[Initializing HelloWorld]
[Running static initializer for HelloWorld]

  詳細輸出還顯示一些內部拋出的異常(如果發生的話)包含堆棧跟蹤

  verbose 解決問題

  詳細輸出有助於解決類路徑問題例如沒有打開 JAR 文件(因此不在類路徑中)或從錯誤的位置裝入了類

  IBM 詳細類裝入

  知道類裝入器在哪裡尋找類特定的類是由哪個類裝入器裝入的通常很有用可以用 IBM 詳細類裝入命令行選項得到這個信息Dibmclverbose=<class name>可以用正則表達式聲明類的名稱例如 Hello* 會跟蹤所有以 Hello 開頭的名稱

  這個選項也可用於用戶定義的類裝入器只要它們直接或間接地擴展了 URLClassLoader

  解釋 IBM 詳細類裝入的輸出

  IBM 詳細類裝入的輸出顯示了要裝入指定類的類裝入器以及它們查找的位置例如假設用以下命令行
java Dibmclverbose=ClassToTrace MainClass

  在這裡MainClass 在它的主方法中引用了 ClassToTrace這會形成像 這裡 一樣的輸出

  在列出類裝入器的時候父類在子女之前列出因為標准的委托模型的工作方式是父類優先

  請注意引導類裝入器沒有輸出只有擴展了 URLClassLoader 的類裝入器才有輸出還請注意類裝入器列在它們的類名之下如果類裝入器有兩個實例那麼可能無法區分它們

    用 IBM 詳細類裝入解決問題

  IBM 詳細類裝入選項是檢查所有類裝入器設置的類路徑的好方法它還可以指出指定類是由哪個類裝入器裝入的從哪裡裝入的這樣就可以容易地看出是否裝入了類的正確版本

  Javadump

  Javadump(也稱為 Javacore)是另一個很有用的 IBM 診斷工具當發生以下事件時JVM 會生成 Javadump


發生致命的本機異常
JVM 用光了堆空間
向 JVM 發送了一個信號(例如在 Windows 上按下了 ControlBreak 或在 Linux 上按下了 Control\)
調用了 comibmjvmDumpJavaDump() 方法

  觸發 Javadump 的時候會把詳細信息記錄到在當前工作目錄下保存的一個有日期戳的文本文件中信息包括線程堆棧等方面的數據以及關於系統中類裝入器的豐富信息

    解釋 Javadump 中的類裝入部分

  Javadump 文件中提供的確切信息取決於 JVM 在哪個平台上運行類裝入器部分包括


定義的類裝入器和它們之間的關系
每個類裝入器裝入的類的列表

  以下是從 Javadump 提取的類裝入器信息的快照
CL subcomponent dump routine
============================
 Classpath Z(D:\jre\lib\corejar)
 Oldjava mode false
 Bootstrapping false
 Verbose class dependencies false
 Class verification VERIFY_REMOTE
 Namespace to classloader x
 Start of cache entry pool xD
 Start of free cache entries xD
 Location of method table xCAA
 Global namespace anchor x
 System classloader shadow x
 Classloader shadows xDBA
 Extension loader xADB
 System classloader xADBB
 Classloader summaries
         : =primordial=extension=shareable=middleware
                   =system=trusted=application=delegating
         ta Loader sun/misc/Launcher$AppClassLoader(xDBA)
                  Shadow xADBB
                  Parent sun/misc/Launcher$ExtClassLoader(xADB)
                 Number of loaded classes
                 Number of cached classes
                 Allocation used for loaded classes
                 Package owner xADBB
         xhst Loader sun/misc/Launcher$ExtClassLoader(xD)
                  Shadow xADB
                  Parent *none*(x)
                 Number of loaded classes
                 Number of cached classes
                 Allocation used for loaded classes
                 Package owner xADB
         phst Loader *System*(x) Shadow x
                 Number of loaded classes
                 Number of cached classes
                 Allocation used for loaded classes
                 Package owner x
 ClassLoader loaded classes
         Loader sun/misc/Launcher$AppClassLoader(xDBA)
                 HelloWorld(xACFE)
         Loader sun/misc/Launcher$ExtClassLoader(xD)
         Loader *System*(x)
                 java/io/WinNTFileSystem(xCD)
                 java/lang/Throwable(xCA)
                 java/lang/IndexOutOfBoundsException(xD)
                 java/lang/UnsatisfiedLinkError(xDD)
classes left out to save space
                 [Ljava/lang/Class;(xCAE)
                 java/io/InputStream(xC)
                 java/lang/Integer$(xCE)
                 java/util/Dictionary(xC)

  在這個示例中只有三個標准類裝入器


系統類裝入器(sun/misc/Launcher$AppClassLoader
擴展類裝入器(sun/misc/Launcher$ExtClassLoader
引導類裝入器(*System*

  Classloader 匯總部分提供了系統中每個類裝入器的細節在這個系列的文章中感興趣的類型是基本擴展系統應用程序 和 委托(用在反射中)其他類型(共享的中間件 和信任的)用在 Persistent Reusable JVM 中它們超出了這個文章系列的范圍(請參閱 Persistent Reusable JVM User Guide 以獲得更多信息在下面的 參考資料 一節中有一個鏈接)這個匯總部分還顯示了父類類裝入器系統類裝入器的父類是 sun/misc/Launcher$ExtClass loader(xADB)這個父類地址對應於父類類裝入器的原始數據結構(叫作 shadow)

  類裝入器裝入的類部分列出了每個類裝入器裝入的類在這個示例中系統類裝入器只裝入了一個類 HelloWorld(在地址 xACFE 上)

    用 Javadump 解決問題

  使用 Javadump 提供的信息可以確定系統中存在哪些類裝入器這包括任何用戶自定義的類裝入器從裝入的類列表中可以找出特定的類是由哪個類裝入器裝入的如果找不到某個類說明系統中的任何一個類裝入器都沒有裝入它(通常會形成 ClassNotFoundException 異常)

  可以用 Javadump 診斷的其他類型的問題包括


類裝入器命名空間問題 類裝入器的命名空間是類裝入器和它裝入的所有類的組合例如如果某個類存在但是由錯誤的類裝入器裝入(有時會造成 NoClassDefFoundError 異常)那麼命名空間就是錯誤的 —— 也就是說類在錯誤的類路徑中為了糾正這種問題可以試著把類放到不同的位置(例如放在正常的 Java 類路徑中)並確保由系統類裝入器裝入它

類裝入器約束問題 在這個系列的最後一篇文章中將討論這種問題的一個示例

  Java 方法跟蹤

  IBM JVM 有一個內置的方法跟蹤工具這樣不需要修改 Java 代碼就可以跟蹤任何 Java 代碼(包括核心系統)中的方法因為這個工具可以提供大量數據所以可以控制跟蹤的級別只獲取需要的信息

  啟動跟蹤的選項取決於 JVM 的發行版下面是一些命令行示例

  在 IBM Java 中運行 HelloWorld 時要跟蹤所有 javalangClassLoader
java thods=java/lang/ClassLoader*() Dibmdgtrcprint=mt HelloWorld

  跟蹤 ClassLoader 中的 loadClass() 方法和 HelloWorld 中的方法也在 IBM Java

java thods=java/lang/ClassLoaderloadClass()HelloWorld*()
  Dibmdgtrcprint=mt HelloWorld

    解釋方法跟蹤的輸出

  這裡 是方法跟蹤輸出的一個示例(用前面一段的第二個命令行)

  跟蹤的每一行都提供了比上面顯示的更多的信息我們來完整地看看上面的一行

:: xAC D   > java/lang/ClassLoaderloadClass Bytecode method
       This = xDB Signature: (Ljava/lang/String;Z)Ljava
/lang/Class;

  這個跟蹤包括


::方法進入或退出的時間戳


xAC線程 ID


D某些高級診斷使用的內部 JVM 跟蹤點


余下的信息顯示是進入(>)還是退出了(<)方法後面跟著方法的細節

    用方法跟蹤解決問題

  可以用方法跟蹤解決不同類型的問題包括


性能熱點 使用時間戳可以發現需要花費相當多時間來執行的方法


掛起 最後的方法項通常是很好的線索可以指明應用程序是否掛起


錯誤對象 使用地址通過與對象的構建函數調用的地址進行比對可以檢查出是不是在正確的對象上調用方法


意外的代碼路徑 通過跟蹤進入和退出點可以看出程序是否采用了意外的代碼路徑


其他錯誤 最後的方法項是對錯誤發生位置的良好提示
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26487.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.