文章說的不錯,就轉下來了。html
原文地址:http://www.importnew.com/6581.htmljava
Java類加載器的做用就是在運行時加載類。Java類加載器基於三個機制:委託、可見性和單一性。委託機制是指將加載一個類的請求交給父類加載器,若是這個父類加載器不可以找到或者加載這個類,那麼再加載它。可見性的原理是子類的加載器能夠看見全部的父類加載器加載的類,而父類加載器看不到子類加載器加載的類。單一性原理是指僅加載一個類一次,這是由委託機制確保子類加載器不會再次加載父類加載器加載過的類。正確理解類加載器可以幫你解決NoClassDefFoundError和java.lang.ClassNotFoundException,由於它們和類的加載相關。類加載器一般也是比較高級的Java面試中的重要考題,Java類加載器和工做原理以及classpath如何運做的常常被問到。Java面試題中也常常出現「一個類是否能被兩個不一樣類加載器加載」這樣的問題。這篇教程中,咱們將學到類加載器是什麼,它的工做原理以及一些關於類加載器的知識點。git
類加載器是一個用來加載類文件的類。Java源代碼經過javac編譯器編譯成類文件。而後JVM來執行類文件中的字節碼來執行程序。類加載器負責加載文件系統、網絡或其餘來源的類文件。有三種默認使用的類加載器:Bootstrap類加載器、Extension類加載器和System類加載器(或者叫做Application類加載器)。每種類加載器都有設定好從哪裏加載類。程序員
除了Bootstrap類加載器是大部分由C來寫的,其餘的類加載器都是經過java.lang.ClassLoader來實現的。github
總結一下,下面是三種類加載器加載類文件的地方:面試
1) Bootstrap類加載器 – JRE/lib/rt.jar數據庫
2) Extension類加載器 – JRE/lib/ext或者java.ext.dirs指向的目錄服務器
3) Application類加載器 – CLASSPATH環境變量, 由-classpath或-cp選項定義,或者是JAR中的Manifest的classpath屬性定義.網絡
我以前已經提到過了,類加載器的工做原理基於三個機制:委託、可見性和單一性。這一節,咱們來詳細看看這些規則,並用一個實例來理解工做原理。下面顯示的是類加載器使用委託機制的工做原理。架構
當一個類加載和初始化的時候,類僅在有須要加載的時候被加載。假設你有一個應用須要的類叫做Abc.class,首先加載這個類的請求由Application類加載器委託給它的父類加載器Extension類加載器,而後再委託給Bootstrap類加載器。Bootstrap類加載器會先看看rt.jar中有沒有這個類,由於並無這個類,因此這個請求由回到Extension類加載器,它會查看jre/lib/ext目錄下有沒有這個類,若是這個類被Extension類加載器找到了,那麼它將被加載,而Application類加載器不會加載這個類;而若是這個類沒有被Extension類加載器找到,那麼再由Application類加載器從classpath中尋找。記住classpath定義的是類文件的加載目錄,而PATH是定義的是可執行程序如javac,java等的執行路徑。
根據可見性機制,子類加載器能夠看到父類加載器加載的類,而反之則不行。因此下面的例子中,當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)
|
根據這個機制,父加載器加載過的類不能被子加載器加載第二次。雖然重寫違反委託和單一性機制的類加載器是可能的,但這樣作並不可取。你寫本身的類加載器的時候應該嚴格遵照這三條機制。
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()將字節碼轉化成類實例,而後返回。
類加載器是個很強大的概念,不少地方被運用。最經典的例子就是AppletClassLoader,它被用來加載Applet使用的類,而Applets大部分是在網上使用,而非本地的操做系統使用。使用不一樣的類加載器,你能夠從不一樣的源地址加載同一個類,它們被視爲不一樣的類。J2EE使用多個類加載器加載不一樣地方的類,例如WAR文件由Web-app類加載器加載,而EJB-JAR中的類由另外的類加載器加載。有些服務器也支持熱部署,這也由類加載器實現。你也可使用類加載器來加載數據庫或者其餘持久層的數據。
以上是關於類加載器的工做原理。咱們已經知道了委託、可見性以及單一性原理,這些對於調試類加載器相關問題時相當重要。這些對於Java程序員和架構師來講都是必不可少的知識。
原文連接: Javarevisited 翻譯: ImportNew.com - 唐小娟
譯文連接: http://www.importnew.com/6581.html
[ 轉載請保留原文出處、譯者和譯文連接。]