ClassLoader, JavaAgent, Aspectj Weaving一站式掃盲帖

jopen 9年前發布 | 14K 次閱讀 Java開發 ClassLoader
 

最近工作里復習的Class Loader基礎知識集錦,寫下來希望對別人有幫助,而且不止是為了撂倒面試官。

為了盡量簡單明了容易背,有些部分寫得比較干。

0. 參考資料:

  • 書:《深入了解Java虛擬機》、《實戰Java虛擬機》
  • 規范: Java語言規范 第12章
  • 源碼: OpenJDK 7 的Java及C代碼( class.c , classloader.c,jvm.cpp)

1. Class裝載的三個階段

1.1 載入 (Load)

從Class文件或別的什么地方載入一段二進制流字節流,把它解釋成永久代里的運行時數據結構,生成一個Class對象。

1.2 鏈接 (Resolve)

將之前載入的數據結構里的符號引用表,解析成直接引用。

中間如果遇到引用的類還沒被加載,就會觸發該類的加載。

可能JDK會很懶惰的在運行某個函數實際使用到該引用時才發生鏈接,也可能在類加載時就解析全部引用。

1.3 初始化 (Initniazle)

初始化靜態變量,并執行靜態初始化語句。

2. Class裝載的時機

  1. ClassLoader.loadClass()
  2. 前文所說的鏈接時觸發的裝載
  3. Class.forName() 等java.lang.reflect反射包
  4. new 構造對象
  5. 初始化子類時,會同時初始化父類
  6. 訪問類的靜態變量或靜態方法(但static final的常量除外,此君在常量池里)

本質上,也是很懶惰的按需加載的,由于類裝載的Lazy和前面解釋引用的Lazy,所以Jar包里有時候有些類用到的了沒在Class Path里的其他類,也能人品爆發的照跑不誤。

除了1,其他幾種方式默認都到達類裝載的初始化階段。

3. ClassLoader.loadClass() 與 Class.forName()

ClassLoader.loadClass(String name, boolean resolve),其中resolve默認為false,即只執行類裝載的第一個階段。

Class.forName(String name, boolean initialize, ClassLoader loader), 其中initialize默認為true,即執行到類裝載的第三個階段。

4. ClassNotFoundException 和 NoClassDefFoundError

ClassLoader.loadClass() 與 Class.forName() 找不到類定義的二進制流時拋出ClassNotFoundException。

鏈接階段解釋引用失敗,找不到引用的類時拋出NoClassDefFoundError。

5. ClassLoader及雙親委派機制

ClassLoader.loadClass()的標準流程:

  1. findLoadedClass() 查看類是否已加載
  2. 如果不存在,則調用parent loader的loadClass()
  3. 如果不存在,調用findClass() 在本ClassLoader的ClassPath里加載該類

所謂雙親委派機制,就是先從parent loader開始查找,找不到了才用自己的findClass()函數去查找,兼顧了效率:避免重復加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次,和安全,避免子類亂加載。

而OSGI或SPI或熱替換方案,則需要破壞這個雙親委托,先調用自己的findClass()。

findClass() 是各個ClassLoader各自實現,各顯神通的地方,從各種奇葩地方載入Class二進制字節流。

但最后都會調用defineClass(),傳入二進制字節流,返回Class對象。留意此處,呆會AspectJ的時候會回到這里。

在JDK6,loadClass()很過分的定義了方法級的synchronized ,在JDK7改成一個以Class Name作Key的 parallelLockMap,增強了并行加載不同Class的能力。

6. System ClassLoader 與 Thread Context Classloader

有時候,看到錯誤日志說張三不是張三,包名類名一樣但instanceof 死活返回 false,唯一原因是它們由兩個不同的ClassLoader加載。

默認的Bootstrap(加載jdk的lib目錄),Extension(加載jdk的lib/ext目錄),Application(加載啟動時定義的classpath)三層ClassLoader機制不再重復。

平時用ClassLoader.getSystemClassLoader()就可以得到sun.misc.Launcher$ApplicationClassLoader 這個Application ClassLoader。

在類A里加載類B,默認使用加載了類A的Loader。但,也有特殊情況,比如JDBC加載driver時的機制,需要在父 ClassLoader(JDBC屬于JDK一部分)里根據配置反射創建jdbc driver的數據實現類,Sun設計了一個特殊方案 --Thread Context Class Loader。

JAXB(比如要在Jar包里找xsd schema文件的時候)也使用了它,所以用到它們時就要注意Thread Context ClassLoader的設置,可以用代碼隨時設置current thread的loader,也可以用自定義的ThreadFactory在創建線程時設置,它默認是父線程的loader,如果都沒設置就是 System ClassLoader。

7. Java Agent機制與AspectJ的LoadTime Weaving

在JDK5開始,在啟動JVM時可增加-javaagent參數,在裝載Class時對類進行動態的修改。

AspectJ的Load Time Weaving機制,需要配置 -javaagent: [path to aspectj-weaver.jar] 。

打開aspectj-weaver.jar,可以看到META-INF/MANIFEST里定義了 Premain-Class: org.aspectj.weaver.loadtime.Agent

再打開這個Agent類,簡化后的代碼大概這個樣子:

ClassFileTransformer s_transformer = new ClassPreProcessorAgentAdapter();

public static void premain(String options, Instrumentation instrumentation) {

instrumentation.addTransformer(s_transformer);

}

可見它的主要作用是將自己的類轉換器注冊到JDK所傳入的Instrumentation。

再看ClassFileTransformer的定義:ClassLoader會在前面defineClass()的過程中,在把二進制字節流轉換為Class對象之前,先把二進制流和當前ClassLoader傳給Transformer,由Transformer加工為另一段二進制字節流返回。

AspectJ就是利用傳入的ClassLoader,找出其Class Path里的META-INF/aop.xml,然后根據aop.xml里的配置進行代碼植入。

測試顯示,加了LoadTime Weaving,類加載的速度明顯變慢,如果是100ms就調用超時的服務,需要做類的預加載。

8. Jar包的預加載

比如有個有趣的需求是加載某個Class A所在的Jar里的全部的Class (怎么好像一點都不有趣)

URL jarUrl = ClassA.getProtectionDomain().getCodeSource().getLocation();

JarFile jarfile = new JarFile(jarUrl.getPath());

Enumeration entries = jarfile.entries();

然后遍歷JarEntry,過濾出后綴為.class的文件,按類名進行裝載就可以了。

9.Class的二進制兼容性

如果Class A 依賴 spring-1.0.jar編譯,當spring升級到spring-2.0.jar,Class A不需要修改代碼也不需要重新編譯,可以直接運行的,spring-2.0.jar就滿足二進制兼容性。

Java語言規范的第13章 有詳細的描述 ,不想直接睡著最好可以找個中文版來看,感謝那些翻譯的同學。

雖然規范的這章看著比較長比較嚇人,但其實二進制兼容性還是很容易做到的,只要你不做把接口改為抽象類之類奇怪的事情,其他一些看起來很大的改動,比如改throws定義,其實都沒有問題。

真的遇到問題,設身處地想想自己是那段Class A的字節碼,現在還能不能跑就行。

感謝你看到這里,希望你只在工作里用到這些知識,祝工作愉快。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!