來自http://www.hmoore.net/book/hx78/java/edit
Java類加載器的作用就是在運行時加載類。Java類加載器基于三個機制:委托、可見性和單一性。委托機制是指將加載一個類的請求交給父類加載器,如果這個父類加載器不能夠找到或者加載這個類,那么再加載它。可見性的原理是子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類。單一性原理是指僅加載一個類一次,這是由委托機制確保子類加載器不會再次加載父類加載器加載過的類。正確理解類加載器能夠幫你解決NoClassDefFoundError和java.lang.ClassNotFoundException,因為它們和類的加載相關。類加載器通常也是比較高級的Java面試中的重要考題,Java類加載器和工作原理以及classpath如何運作的經常被問到。Java面試題中也經常出現“一個類是否能被兩個不同類加載器加載”這樣的問題。這篇教程中,我們將學到類加載器是什么,它的工作原理以及一些關于類加載器的知識點。
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E4%BB%80%E4%B9%88%E6%98%AF%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8)什么是類加載器
類加載器是一個用來加載類文件的類。Java源代碼通過javac編譯器編譯成類文件。然后JVM來執行類文件中的字節碼來執行程序。類加載器負責加載文件系統、網絡或其他來源的類文件。有三種默認使用的類加載器:Bootstrap類加載器、Extension類加載器和System類加載器(或者叫作Application類加載器)。每種類加載器都有設定好從哪里加載類。
* Bootstrap類加載器負責加載rt.jar中的JDK類文件,它是所有類加載器的父加載器。Bootstrap類加載器沒有任何父類加載器,如果你調用String.class.getClassLoader(),會返回null,任何基于此的代碼會拋出NUllPointerException異常。Bootstrap加載器被稱為初始類加載器。
* 而Extension將加載類的請求先委托給它的父加載器,也就是Bootstrap,如果沒有成功加載的話,再從jre/lib/ext目錄下或者java.ext.dirs系統屬性定義的目錄下加載類。Extension加載器由sun.misc.Launcher$ExtClassLoader實現。
* 第三種默認的加載器就是System類加載器(又叫作Application類加載器)了。它負責從classpath環境變量中加載某些應用相關的類,classpath環境變量通常由-classpath或-cp命令行選項來定義,或者是JAR中的Manifest的classpath屬性。Application類加載器是Extension類加載器的子加載器。通過sun.misc.Launcher$AppClassLoader實現。
除了Bootstrap類加載器是大部分由C來寫的,其他的類加載器都是通過java.lang.ClassLoader來實現的。
總結一下,下面是三種類加載器加載類文件的地方:
1) Bootstrap類加載器 – JRE/lib/rt.jar
2) Extension類加載器 – JRE/lib/ext或者java.ext.dirs指向的目錄
3) Application類加載器 – CLASSPATH環境變量, 由-classpath或-cp選項定義,或者是JAR中的Manifest的classpath屬性定義.
[](http://www.importnew.com/?attachment_id=6582)
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86-1)類加載器的工作原理
我之前已經提到過了,類加載器的工作原理基于三個機制:委托、可見性和單一性。這一節,我們來詳細看看這些規則,并用一個實例來理解工作原理。下面顯示的是類加載器使用委托機制的工作原理。
#### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%A7%94%E6%89%98%E6%9C%BA%E5%88%B6)委托機制
當一個類加載和初始化的時候,類僅在有需要加載的時候被加載。假設你有一個應用需要的類叫作Abc.class,首先加載這個類的請求由Application類加載器委托給它的父類加載器Extension類加載器,然后再委托給Bootstrap類加載器。Bootstrap類加載器會先看看rt.jar中有沒有這個類,因為并沒有這個類,所以這個請求由回到Extension類加載器,它會查看jre/lib/ext目錄下有沒有這個類,如果這個類被Extension類加載器找到了,那么它將被加載,而Application類加載器不會加載這個類;而如果這個類沒有被Extension類加載器找到,那么再由Application類加載器從classpath中尋找。記住classpath定義的是類文件的加載目錄,而PATH是定義的是可執行程序如javac,java等的執行路徑。
[](http://www.importnew.com/6581.html/java-classloader-working)
#### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%8F%AF%E8%A7%81%E6%80%A7%E6%9C%BA%E5%88%B6)可見性機制
根據可見性機制,子類加載器可以看到父類加載器加載的類,而反之則不行。所以下面的例子中,當Abc.class已經被Application類加載器加載過了,然后如果想要使用Extension類加載器加載這個類,將會拋出java.lang.ClassNotFoundException異常。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
`package` `test;`
`import` `java.util.logging.Level;`
`import` `java.util.logging.Logger;`
`/**`
`* Java program to demonstrate How ClassLoader works in Java,`
`* in particular about visibility principle of ClassLoader.`
`*`
`* @author Javin Paul`
`*/`
`public` `class` `ClassLoaderTest {`
`public` `static` `void` `main(String args[]) {`
`try` `{?????????`
`//printing ClassLoader of this class`
`System.out.println(``"ClassLoaderTest.getClass().getClassLoader() : "`
`+ ClassLoaderTest.``class``.getClassLoader());`
`//trying to explicitly load this class again using Extension class loader`
`Class.forName(``"test.ClassLoaderTest"``,` `true`
`,? ClassLoaderTest.``class``.getClassLoader().getParent());`
`}` `catch` `(ClassNotFoundException ex) {`
`Logger.getLogger(ClassLoaderTest.``class``.getName()).log(Level.SEVERE,` `null``, ex);`
`}`
`}`
`}`
|
輸出:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
`ClassLoaderTest.getClass().getClassLoader() : sun.misc.Launcher$AppClassLoader@601bb1`
`16/08/2012 2:43:48 AM test.ClassLoaderTest main`
`SEVERE: null`
`java.lang.ClassNotFoundException: test.ClassLoaderTest`
`at java.net.URLClassLoader$1.run(URLClassLoader.java:202)`
`at java.security.AccessController.doPrivileged(Native Method)`
`at java.net.URLClassLoader.findClass(URLClassLoader.java:190)`
`at sun.misc.Launcher$ExtClassLoader.findClass(Launcher.java:229)`
`at java.lang.ClassLoader.loadClass(ClassLoader.java:306)`
`at java.lang.ClassLoader.loadClass(ClassLoader.java:247)`
`at java.lang.Class.forName0(Native Method)`
`at java.lang.Class.forName(Class.java:247)`
`at test.ClassLoaderTest.main(ClassLoaderTest.java:29)`
|
#### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%8D%95%E4%B8%80%E6%80%A7%E6%9C%BA%E5%88%B6)單一性機制
根據這個機制,父加載器加載過的類不能被子加載器加載第二次。雖然重寫違反委托和單一性機制的類加載器是可能的,但這樣做并不可取。你寫自己的類加載器的時候應該嚴格遵守這三條機制。
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%A6%82%E4%BD%95%E6%98%BE%E5%BC%8F%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%B1%BB)如何顯式的加載類
Java提供了顯式加載類的API:Class.forName(classname)和Class.forName(classname, initialized, classloader)。就像上面的例子中,你可以指定類加載器的名稱以及要加載的類的名稱。類的加載是通過調用java.lang.ClassLoader的loadClass()方法,而loadClass()方法則調用了findClass()方法來定位相應類的字節碼。在這個例子中Extension類加載器使用了java.net.URLClassLoader,它從JAR和目錄中進行查找類文件,所有以”/”結尾的查找路徑被認為是目錄。如果findClass()沒有找到那么它會拋出java.lang.ClassNotFoundException異常,而如果找到的話則會調用defineClass()將字節碼轉化成類實例,然后返回。
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E4%BB%80%E4%B9%88%E5%9C%B0%E6%96%B9%E4%BD%BF%E7%94%A8%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8)什么地方使用類加載器
類加載器是個很強大的概念,很多地方被運用。最經典的例子就是AppletClassLoader,它被用來加載Applet使用的類,而Applets大部分是在網上使用,而非本地的操作系統使用。使用不同的類加載器,你可以從不同的源地址加載同一個類,它們被視為不同的類。J2EE使用多個類加載器加載不同地方的類,例如WAR文件由Web-app類加載器加載,而EJB-JAR中的類由另外的類加載器加載。有些服務器也支持熱部署,這也由類加載器實現。你也可以使用類加載器來加載數據庫或者其他持久層的數據。
以上是關于類加載器的工作原理。我們已經知道了委托、可見性以及單一性原理,這些對于調試類加載器相關問題時至關重要。這些對于Java程序員和架構師來說都是必不可少的知識。
原文鏈接:?[Javarevisited](http://javarevisited.blogspot.hk/2012/12/how-classloader-works-in-java.html)?翻譯:?[ImportNew.com?](http://www.importnew.com/)-?[唐小娟](http://www.importnew.com/author/tangxiaojuan)
譯文鏈接:?[http://www.importnew.com/6581.html](http://www.importnew.com/6581.html)
[?**轉載請保留原文出處、譯者和譯文鏈接。**]
- JVM
- 深入理解Java內存模型
- 深入理解Java內存模型(一)——基礎
- 深入理解Java內存模型(二)——重排序
- 深入理解Java內存模型(三)——順序一致性
- 深入理解Java內存模型(四)——volatile
- 深入理解Java內存模型(五)——鎖
- 深入理解Java內存模型(六)——final
- 深入理解Java內存模型(七)——總結
- Java內存模型
- Java內存模型2
- 堆內內存還是堆外內存?
- JVM內存配置詳解
- Java內存分配全面淺析
- 深入Java核心 Java內存分配原理精講
- jvm常量池
- JVM調優總結
- JVM調優總結(一)-- 一些概念
- JVM調優總結(二)-一些概念
- VM調優總結(三)-基本垃圾回收算法
- JVM調優總結(四)-垃圾回收面臨的問題
- JVM調優總結(五)-分代垃圾回收詳述1
- JVM調優總結(六)-分代垃圾回收詳述2
- JVM調優總結(七)-典型配置舉例1
- JVM調優總結(八)-典型配置舉例2
- JVM調優總結(九)-新一代的垃圾回收算法
- JVM調優總結(十)-調優方法
- 基礎
- Java 征途:行者的地圖
- Java程序員應該知道的10個面向對象理論
- Java泛型總結
- 序列化與反序列化
- 通過反編譯深入理解Java String及intern
- android 加固防止反編譯-重新打包
- volatile
- 正確使用 Volatile 變量
- 異常
- 深入理解java異常處理機制
- Java異常處理的10個最佳實踐
- Java異常處理手冊和最佳實踐
- Java提高篇——對象克隆(復制)
- Java中如何克隆集合——ArrayList和HashSet深拷貝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常見正則表達式
- 類
- 理解java類加載器以及ClassLoader類
- 深入探討 Java 類加載器
- 類加載器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之實現細節
- java.util.concurrent 之ConcurrentHashMap 源碼分析
- HashMap的實現原理和底層數據結構
- 線程
- 關于Java并發編程的總結和思考
- 40個Java多線程問題總結
- Java中的多線程你只要看這一篇就夠了
- Java多線程干貨系列(1):Java多線程基礎
- Java非阻塞算法簡介
- Java并發的四種風味:Thread、Executor、ForkJoin和Actor
- Java中不同的并發實現的性能比較
- JAVA CAS原理深度分析
- 多個線程之間共享數據的方式
- Java并發編程
- Java并發編程(1):可重入內置鎖
- Java并發編程(2):線程中斷(含代碼)
- Java并發編程(3):線程掛起、恢復與終止的正確方法(含代碼)
- Java并發編程(4):守護線程與線程阻塞的四種情況
- Java并發編程(5):volatile變量修飾符—意料之外的問題(含代碼)
- Java并發編程(6):Runnable和Thread實現多線程的區別(含代碼)
- Java并發編程(7):使用synchronized獲取互斥鎖的幾點說明
- Java并發編程(8):多線程環境中安全使用集合API(含代碼)
- Java并發編程(9):死鎖(含代碼)
- Java并發編程(10):使用wait/notify/notifyAll實現線程間通信的幾點重要說明
- java并發編程-II
- Java多線程基礎:進程和線程之由來
- Java并發編程:如何創建線程?
- Java并發編程:Thread類的使用
- Java并發編程:synchronized
- Java并發編程:Lock
- Java并發編程:volatile關鍵字解析
- Java并發編程:深入剖析ThreadLocal
- Java并發編程:CountDownLatch、CyclicBarrier和Semaphore
- Java并發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
- Synchronized與Lock
- JVM底層又是如何實現synchronized的
- Java synchronized詳解
- synchronized 與 Lock 的那點事
- 深入研究 Java Synchronize 和 Lock 的區別與用法
- JAVA編程中的鎖機制詳解
- Java中的鎖
- TreadLocal
- 深入JDK源碼之ThreadLocal類
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的內存泄露
- 多線程設計模式
- Java多線程編程中Future模式的詳解
- 原子操作(CAS)
- [譯]Java中Wait、Sleep和Yield方法的區別
- 線程池
- 如何合理地估算線程池大小?
- JAVA線程池中隊列與池大小的關系
- Java四種線程池的使用
- 深入理解Java之線程池
- java并發編程III
- Java 8并發工具包漫游指南
- 聊聊并發
- 聊聊并發(一)——深入分析Volatile的實現原理
- 聊聊并發(二)——Java SE1.6中的Synchronized
- 文件
- 網絡
- index
- 內存文章索引
- 基礎文章索引
- 線程文章索引
- 網絡文章索引
- IOC
- 設計模式文章索引
- 面試
- Java常量池詳解之一道比較蛋疼的面試題
- 近5年133個Java面試問題列表
- Java工程師成神之路
- Java字符串問題Top10
- 設計模式
- Java:單例模式的七種寫法
- Java 利用枚舉實現單例模式
- 常用jar
- HttpClient和HtmlUnit的比較總結
- IO
- NIO
- NIO入門
- 注解
- Java Annotation認知(包括框架圖、詳細介紹、示例說明)