類加載器
虛擬機設計團隊把類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節流”這個動作放到java虛擬機的外部去實現。以便讓應用程序自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱爲類加載器。
類加載器可以說是java語言的一項創新,也是java語言流行的重要原因之一。它最初是爲了滿足java Applet的需求而開發出來的。雖然目前java Applet技術基本上已經“死掉”。但是類加載器卻在類層次劃分、OSGi、熱部署、代碼加密等領域大放異彩,成爲了java技術體系中一塊重要的基石,可謂是失之東隅,收之桑榆。
類加載器雖然只用於實現類加載動作,但它在java程序中起到的作用遠遠不限於類加載階段。對於任意一個類,都需要由加載它的類加載器和這個類本身確立在java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。這句話可以表達的更通俗一點;比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源於同一個class文件,被同一虛擬機加載,只要加載它們的類加載器不同,那麼這兩個類就必定不相等。
這裏所指的“相等”,包括代表類的class對象的equals()方法,isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用instanceof關鍵字做對象所屬關係的判定等情況。如果沒有注意到類加載器的影響,在某些情況下可能會產生具有迷惑性的結果。
package com.ggp.jvm;
import java.io.IOException;
import java.io.InputStream;
/**
* @author: ggp
* @Date: 2019/4/24 08:46
* @Description:
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name)throws ClassNotFoundException{
try{
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
/**
* 本類的同路徑下
*/
InputStream is = this.getClass().getResourceAsStream(fileName);
/**
* 如果未找到class文件,就調用父類的loadClass();
*/
if(is == null){
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
/**
* 生成class對象
*/
return defineClass(name,b,0,b.length);
}catch(IOException e){
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("com.ggp.jvm.ClassLoaderTest").newInstance();
ClassLoaderTest test = new ClassLoaderTest();
System.out.println("*******obj class**********");
System.out.println(obj.getClass());
System.out.println("*******test class**********");
System.out.println(test.getClass());
System.out.println("*******obj classLoader**********");
System.out.println(obj.getClass().getClassLoader());
System.out.println("*******test classLoader**********");
System.out.println(test.getClass().getClassLoader());
System.out.println("*******obj instanceof com.ggp.jvm.ClassLoaderTest**********");
System.out.println(obj instanceof com.ggp.jvm.ClassLoaderTest );
System.out.println("*******obj class equal test class**********");
System.out.println(obj.getClass().equals(test.getClass()));
System.out.println("*******obj class isAssignableFrom test**********");
System.out.println(obj.getClass().isAssignableFrom(test.getClass()));
}
}
打印結果
*******obj class**********
class com.ggp.jvm.ClassLoaderTest
*******test class**********
class com.ggp.jvm.ClassLoaderTest
*******obj classLoader**********
com.ggp.jvm.ClassLoaderTest$1@8813f2
*******test classLoader**********
sun.misc.Launcher$AppClassLoader@1a46e30
*******obj instanceof com.ggp.jvm.ClassLoaderTest**********
false
*******obj class equal test class**********
false
*******obj class isAssignableFrom test**********
false
關於自定類的加載器可觀看我的另一篇博客https://blog.csdn.net/qq_33543634/article/details/89500650
雙親委派模型
從java虛擬機的角度來講,只存在兩種不同的類加載器;一種是啓動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現,是虛擬機自身的一部分(只限於Hotspot);另一種就是所有其他的類加載器,這些類加載器都由java語言實現,獨立於虛擬機外部,並且全部繼承自抽象類java.lang.ClassLoader
當然有更細的劃分,如下圖
- 啓動類加載器(BootStrap ClassLoader):這個類加載器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是被虛擬機識別的類庫(按照文件名識別,後綴之類)加載到內存中。啓動類加載器無法被用戶直接使用。用戶在編寫自定義類加載器的時候,如果需要把加載請求委派給引導類加載器,那直接使用null代替即可。
- 擴展類加載器(Extension ClassLoader): 這個類加載器有sun.misc.Lauch$ExtClassLoader實現,它負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器
- 應用程序類加載器(Application ClassLoader) : 這個類加載器由sun.misc.Launcher$AppClassLoader實現。由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也一般稱爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義自己的類加載器,一般情況下這個就是程序中的默認的類加載器
jdk源碼關於loadClass的實現
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
首先會去判斷這個類是否已經被加載,如果沒有,先去判斷它有沒有父類加載器,如果有,就用父類加載器去加載,如果沒有就用啓動類加載器去加載,如果上面依舊沒有加載成功,就用自己的加載器去加載
值得注意的一點是類加載的父子關係並不是通過繼承來實現的,而都是通過組合的方式
什麼是組合
Class A
Class B {
class B(){
A a = new A();
}
}
上面的就是組合而不是通過關鍵字extends繼承
雙親委派模型的優點
使用雙親委派模型來組織類加載器之間的關係,有一個顯而易見的好處就是java類隨着它的類加載器一起具備了一種帶有優先級的層次關係。例如類java.lang.Object,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是要委派給處於模型最頂端的啓動類加載器進行加載,因此Object類在程序中各個類加載環境中都是同一個類。因此這個模型有利於核心類不被修改。
雙親委派模型的破壞
- 雙親委派模型是在jdk1.2之後引入的,而類加載器和抽象類java.lang.ClassLoader則是在jdk1.0已經存在,面對已經存在的用戶自定義類加載器實現代碼,爲了前後兼容。設計者給java.lang.ClassLoader添加了一個新的protected方法findClass(),然後通過這個方法去調用自己loadClass()
- 第二次是這個模型本身的缺陷造成的。基礎類之所以被稱爲基礎,因爲它們經常被作爲用戶代碼調用的api,但是基礎類如果需要回調用戶的代碼,怎麼辦?一個典型的例子就是JDNI服務,他的代碼是由啓動類加載,但是它的目的是對資源進行集中管理和查找,它需要調用有獨立廠商實現並部署在應用程序下的spi代碼,但是啓動類加載器不可能認識這些代碼,爲了解決這個問題,設計者引入了一個線程上下文加載器,JNDI服務通過這個線程上下文加載器完成通過父類加載器請求子類加載器去加載
- 第三次破壞是由我們常聽到的熱部署之類的,不再細說
參考資料:《深入理解java虛擬機》