早期的java程序員可能只要懂基本語法,還有少數的項目經驗就可以找到一份比較好的工作。Java和java社區的發展,更多的人了解它,深入它。現在java程序員了解一些語法我看還遠遠不夠了,對于jvm的了解和深入是非常重要的。網民的增多,網站的剛性需求,很多網站面臨高性能,高并發等等一系列的問題。沒有深入jvm的java程序員是很難寫出高質量高并發的代碼(也許一棒子打死所有人了,但我想絕大部分是肯定的)。
Osgi也許你并不陌生,但是他底層的實現機制你可能沒去了解過。如果你是個打破砂鍋問到底的人,你肯定會想知道osgi是如何做到的。但是你沒有了解jvm的類加載體系,你肯定很難理解osgi是如果做到類隔離等一系列的問題。不過想完整理解osgi還需要其他很多方面的知識,但是它基本的機制還是的了解jvm的類加載機制。
Java類庫有些包只是定義了一個標準,具體的實現都是由具體的供應商來提供。Java與數據庫連接就是一個很好的例子。Java.sql類庫只是定義了java與數據庫連接的標準,那么與mysql就需要msyql的驅動,oracle就需要oracle的驅動,而java.sql類庫是由bootstrap classloader加載,驅動包中的類是由system classloader來加載,不同類加載器加載的類是無法相互認識,所以自然也無法正常提供功能了。jvm又是提供了什么機制讓他們交互呢?如果你確實對這些問題毫無頭緒的話,那么我覺得你真的要好好理解下jvm類加載體系。
這篇文章主要是介紹下jvm類加載的機制基礎知識。關于其他相關涉及,有時間的話,我會單獨寫文章來介紹。
1、 java類加載器

1.1 Bootstrap classloader:sun jdk是用c++實現,所以在代碼中你是無法獲取此加載器的實例。此加載器主要負責加載$JAVA_HOME/jre/lib/rt.jar。java類中獲取結果為null,這里可以用一個例子跑下證明:
public class Test {
public static void main(String[] arg) throws Exception{
ClassLoader classloader = Test.class.getClassLoader();
System.out.println(classloader);
System.out.println(classloader.getParent());
System.out.println(classloader.getParent().getParent());
}
}
輸出結果:
sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$ExtClassLoader@addbf1
null
最后輸出的null就是代表bootstrap classloader。
1.2 Extension classloader:主要加載擴展功能的jar,$JAVA_HOME/jre/lib/ext/*.jar。
1.3 System classloader:加載claspath中的jar包。
1.4自定義 classloader:主要加載你指定的目錄中的包和類。
2、 雙親委派模型
系統運行時,我們請求加載String類,此時System Classloader自己不找classpath中的包,把請求轉發給Extension Classloader,但它也不做查找,又轉發給Bootstrap Classloader,但是它發現自己沒有parent了。于是他在rt.jar包中找到String類并加載到jvm中提供使用。Jvm為什么要這么實現呢?其實和java安全體系有關。假設jvm不是這么實現,我們自定義一個String類,做一些破壞,那么運行jvm的機器肯定要受到損壞。具體例子:
public class String {
public static void main(String[] args) {
System.out.println("hello world");
}
}
我們運行自定義String類的時候報錯了,說沒有main方法,可我們定義的明明有的,嘿嘿,委派機制的緣故最后加載到的是由bootstrap classloader在rt.jar包中的String,那個類是沒有main方法,因此報錯了。

3、 類隔離
jvm中的類是:類加載器+包名+類名。比如:URLClassLoader1,URLClassLoader2分別加載com.test.Test的時候會加載兩次,因為每個classloader中的類對于其他classloader來說是隔離的,不認識的。例子:
import java.net.URL;
import java.net.URLClassLoader;
public class CustomClassloaderTest {
public static void main(String[] args) throws Exception {
URL url = new URL("file:/g:/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class c = ucl.loadClass("Yang");
c.newInstance();
System.out.println(c.getClassLoader());
URLClassLoader ucl2 = new URLClassLoader(new URL[]{url});
Class c2 = ucl2.loadClass("Yang");
c2.newInstance();
System.out.println(c2.getClassLoader());
}
}
大家把Yang類存在g盤下。
public class Yang {
static {
System.out.println("Yang");
}
}
運行結果:
Yang
java.net.URLClassLoader@c17164
Yang
java.net.URLClassLoader@61de33
看到每次加載Yang類的時候都輸出Yang,說明Yang類被加載了兩次。
如果你不確信,可以修改下代碼,讓同一classloader加載Yang類兩次
import java.net.URL;
import java.net.URLClassLoader;
public class CustomClassloaderTest {
public static void main(String[] args) throws Exception {
URL url = new URL("file:/g:/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class c = ucl.loadClass("Yang");
c.newInstance();
System.out.println(c.getClassLoader());
Class c2 = ucl.loadClass("Yang");
c2.newInstance();
System.out.println(c2.getClassLoader());
}
}
看看輸出結果:
Yang
java.net.URLClassLoader@c17164
java.net.URLClassLoader@c17164
結果中只輸出了一次Yang。因此可以證明我們最開始說的類隔離。
4、 線程上下文類加載器
我們理解了雙親委派模型,那么目前只有由下向上單向尋找類(system->extension->bootstrap)
。我們在最開始的時候說過,java.sql包中的類由bootstrap或extension classloader加載,而mysql驅動包是在classpath中由system來加載,但bootstrap中的類是無法找到system classloader中的類,此時靠線程上下文類加載器來解決。線程上下文類加載器主要就是能讓jvm類加載模型具有了向下尋找的可能,bootstrap->extension->system,如果不做任何設置,線程上下文類加載器默認是system classloader。本來這里想寫一個例子的,可是有點麻煩,所以下次單獨寫一篇關于這方面的知識。