一、java提供了三種ClassLoader對Class進行加載:
1.BootStrap ClassLoader:稱為啟動類加載器,是Java類加載層次中最頂層的類加載器,負責加載JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等,可通過如下程序獲得該類加載器從哪些地方加載了相關的jar或class文件:
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
或者
System.out.println(System.getProperty("sun.boot.class.path"));
最後結果為:
/Java/jdk1.6.0_22/jre/lib/resources.jar
/Java/jdk1.6.0_22/jre/lib/rt.jar
/Java/jdk1.6.0_22/jre/lib/sunrsasign.jar
/Java/jdk1.6.0_22/jre/lib/jsse.jar
/Java/jdk1.6.0_22/jre/lib/jce.jar
/Java/jdk1.6.0_22/jre/lib/charsets.jar
/Java/jdk1.6.0_22/jre/classes/
2.Extension ClassLoader:稱為擴展類加載器,負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目下的所有jar。
3.App ClassLoader:稱為系統類加載器,負責加載應用程序classpath目錄下的所有jar和class文件。
二、ClassLoader的加載原理
ClassLoader使用的是雙親委托模型來搜索類的,每個ClassLoader實例都有一個父類加載器的引用(不是繼承的關系,是一個包含的關系),虛擬機內置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器,但可以用作其它ClassLoader實例的的父類加載器。當一個ClassLoader實例需要加載某個類時,它會試圖親自搜索某個類之前,先把這個任務委托給它的父類加載器,這個過程是由上至下依次檢查的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒加載到,則把任務轉交給Extension ClassLoader試圖加載,如果也沒加載到,則轉交給App ClassLoader 進行加載,如果它也沒有加載得到的話,則返回給委托的發起者,由它到指定的文件系統或網絡等URL中加載該類。如果它們都沒有加載到這個類時,則拋出ClassNotFoundException異常。否則將這個找到的類生成一個類的定義,並將它加載到內存當中,最後返回這個類在內存中的Class實例對象。
因為這樣可以避免重復加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義的類型,這樣會存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因為String已經在啟動時就被引導類加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠也無法加載一個自己寫的String,除非你改變JDK中ClassLoader搜索類的默認算法。
JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同,而且要判斷是否由同一個類加載器實例加載的。只有兩者同時滿足的情況下,JVM才認為這兩個class是相同的。就算兩個class是同一份class字節碼,如果被兩個不同的ClassLoader實例所加載,JVM也會認為它們是兩個不同class。

三、自定義ClassLoader,自定義ClassLoader需要繼承java.lang.ClassLoader或者繼承URLClassLoader
放兩個類型的具體實現代碼:
1.繼承自ClassLoader
public class NetworkClassLoader extends ClassLoader {
private String rootUrl;
public NetworkClassLoader(String rootUrl) {
this.rootUrl = rootUrl;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;//this.findLoadedClass(name); // 父類已加載
//if (clazz == null) { //檢查該類是否已被加載過
byte[] classData = getClassData(name); //根據類的二進制名稱,獲得該class文件的字節碼數組
if (classData == null) {
throw new ClassNotFoundException();
}
clazz = defineClass(name, classData, 0, classData.length); //將class的字節碼數組轉換成Class類的實例
//}
return clazz;
}
private byte[] getClassData(String name) {
InputStream is = null;
try {
String path = classNameToPath(name);
URL url = new URL(path);
byte[] buff = new byte[1024*4];
int len = -1;
is = url.openStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while((len = is.read(buff)) != -1) {
baos.write(buff,0,len);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private String classNameToPath(String name) {
return rootUrl + "/" + name.replace(".", "/") + ".class";
}
}
2.繼承自URLClassLoader
ublic class SimpleURLClassLoader extends URLClassLoader {
//工程class類所在的路徑
public static String projectClassPath = "E:/IDE/work_place/ZJob-Note/bin/";
//所有的測試的類都在同一個包下
public static String packagePath = "testjvm/testclassloader/";
public SimpleURLClassLoader() {
//設置ClassLoader加載的路徑
super(getMyURLs());
}
private static URL[] getMyURLs(){
URL url = null;
try {
url = new File(projectClassPath).toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return new URL[] { url };
}
public Class load(String name) throws Exception{
return loadClass(name);
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name,false);
}
/**
* 重寫loadClass,不采用雙親委托機制("java."開頭的類還是會由系統默認ClassLoader加載)
*/
@Override
public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
Class clazz = null;
//查看HotSwapURLClassLoader實例緩存下,是否已經加載過class
clazz = findLoadedClass(name);
if (clazz != null ) {
if (resolve) {
resolveClass(clazz);
}
return (clazz);
}
//如果類的包名為"java."開始,則有系統默認加載器AppClassLoader加載
if(name.startsWith("java.")) {
try {
//得到系統默認的加載cl,即AppClassLoader
ClassLoader system = ClassLoader.getSystemClassLoader();
clazz = system.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
return customLoad(name,this);
}
/**
* 自定義加載
* @param name
* @param cl
* @return
* @throws ClassNotFoundException
*/
public Class customLoad(String name,ClassLoader cl) throws ClassNotFoundException {
return customLoad(name, false,cl);
}
/**
* 自定義加載
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
public Class customLoad(String name, boolean resolve,ClassLoader cl) throws ClassNotFoundException {
//findClass()調用的是URLClassLoader裡面重載了ClassLoader的findClass()方法
Class clazz = ((SimpleURLClassLoader)cl).findClass(name);
if (resolve)
((SimpleURLClassLoader)cl).resolveClass(clazz);
return clazz;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
四、ClassLoader卸載Class
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):
GC的時機我們是不可控的,那麼同樣的我們對於Class的卸載也是不可控的。
package testjvm.testclassloader;
public class TestClassUnLoad {
public static void main(String[] args) throws Exception {
SimpleURLClassLoader loader = new SimpleURLClassLoader();
// 用自定義的加載器加載A
Class clazzA = loader.load("testjvm.testclassloader.A");
Object a = clazzA.newInstance();
// 清除相關引用
a = null; //清除該類的實例
clazzA = null; //清除該class對象的引用
loader = null; //清楚該類的ClassLoader引用
// 執行一次gc垃圾回收
System.gc();
System.out.println("GC over");
}
}
參考文檔:
http://blog.csdn.net/xyang81/article/details/7292380
https://my.oschina.net/xianggao/blog/367822