上一章我們講到,一個類加載到內存裏我們可以操作的部分只有兩個,一個是加載部分一個是static{},我相信static{}不用多講了。
接下來我們就來解析一下ClassLoader即類加載器,他就是用來加載字節碼到方法區的類。
當年出現ClassLoader這個東西動態加載類的字節碼主要還是爲了滿足JavaApplet的需求。雖然後來JavaApplet掛掉了,但是ClassLoader這個形式還是保留了下來,而且活的很好。
類的相等和instanceOf:
來我們來寫一個例子
public class ClassLoaderTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
ClassLoader loader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
try {
String className = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(className);
if(is ==null){
return super.loadClass(name);
}
byte[] buffer = new byte[is.available()];
is.read(buffer);
return defineClass(name, buffer, 0, buffer.length);
} catch (Exception e) {
// TODO: handle exception
throw new ClassNotFoundException(name);
}
}
};
Object object = loader.loadClass("top.jjust.jvm.ClassLoaderTest").newInstance();
System.out.println(object.getClass());
System.out.println(object instanceof top.jjust.jvm.ClassLoaderTest);
}
}
上述代碼先重寫了一個loadClass方法,然後用重寫的方法加載了自己這個類並生成實例。
兩行輸出結果如下
<span style="font-size:12px;">class top.jjust.jvm.ClassLoaderTest
false</span>
我們可以看到class是沒錯的,但是我們用重寫loadClass讀取的class文件卻不被認爲是原類的一個子類(可能說起來比較拗口,大家可以看看代碼就明白了)
這邊牽扯到一個相等的問題,判斷兩個類是否相等(equals、isAssgnableFrom、isInstance、instanceof)有一個前提就是:這兩個類由同一個類加載器加載。如果兩個不同的加載器加載,判斷是一定不等的。
雙親委派模型
在jvm中,有兩種不一樣的類加載器,
一個是加載<JAVA_HOME>/lib下的文件,也就是jvm本身的類(並且jvm會識別名字,單純放在目錄下是沒用的),也就是加載自身的類加載器
還有一種是加載其他類的類加載器。這個種類的類加載器又可以細分爲擴展類加載器和應用程序類加載器。
擴展類加載器主要是加載<JAVA_HOME>/lib/ext文件下的類庫而應用程序類加載器主要是加載用戶類路徑上指定的類庫,平時getsystemClassLoader就是返回的它,程序裏沒有定義過自己的類加載器一般情況下也是用它。這幾個類加載器我們用圖片表示一下。
在類中加載需要按照一種層次,這種層次我們畫在下面:
這是什麼意思?也就是說碰到一個類需要加載時,先要把這個請求交給父類,直到頂層,如果Bootstrap ClassLoader說我不做這個,纔會由下一層嘗試加載。如果所有父類都不能加載,纔會自己加載。
爲什麼要設計這種模型呢?我們具一格例子,就拿所有的父類Object講,這個類一般是由Bootstrap ClassLoader加載的,如果不使用雙親委派模型,在自定義加載器中加載了這個Objcet,那麼一個jvm中就會出現多個Objcet。而不是像雙親委派模型一樣,都由Bootstrap ClassLoader加載,不會重複加載。
其實在重寫方法的時候,建議重寫findClass()方法而不是loadClass,下面是loadClass的源碼
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
我們發現,如果我們重寫loadClass則會破壞雙親委派模型。