今天學習一下ClassLoader的工作流程,在網上找了些資料,整理如下:
1.ClassLoader簡述
JVM在運行時會產生三個ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.
其中,Bootstrap是用C++編寫的,我們在Java中看不到它,是null。
Extension ClassLoader用來加載擴展類,即/lib/ext中的類。最後AppClassLoader纔是加載Classpath的。ClassLoader加載類用的是委託模型。即先讓Parent類(而不是Super,不是繼承關係)尋找,Parent找不到才自己找。
三者的關係爲:AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent爲Bootstrap ClassLoader。
加載一個類時,首先BootStrap先進行尋找,找不到再由ExtClassLoader尋找,最後纔是AppClassLoader。
爲什麼要設計的這麼複雜呢?其中一個重要原因就是安全性。比如在Applet中,如果編寫了一個java.lang.String類並具有破壞性。假如不採用這種委託機制,就會將這個具有破壞性的String加載到了用戶機器上,導致破壞用戶安全。但採用這種委託機制則不會出現這種情況。因爲要加載java.lang.String類時,系統最終會由Bootstrap進行加載,這個具有破壞性的String永遠沒有機會加載。
其中,Bootstrap是用C++編寫的,我們在Java中看不到它,是null。
Extension ClassLoader用來加載擴展類,即/lib/ext中的類。最後AppClassLoader纔是加載Classpath的。ClassLoader加載類用的是委託模型。即先讓Parent類(而不是Super,不是繼承關係)尋找,Parent找不到才自己找。
三者的關係爲:AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent爲Bootstrap ClassLoader。
加載一個類時,首先BootStrap先進行尋找,找不到再由ExtClassLoader尋找,最後纔是AppClassLoader。
爲什麼要設計的這麼複雜呢?其中一個重要原因就是安全性。比如在Applet中,如果編寫了一個java.lang.String類並具有破壞性。假如不採用這種委託機制,就會將這個具有破壞性的String加載到了用戶機器上,導致破壞用戶安全。但採用這種委託機制則不會出現這種情況。因爲要加載java.lang.String類時,系統最終會由Bootstrap進行加載,這個具有破壞性的String永遠沒有機會加載。
//A.java
public class A{
public static void main(String[] args){
A a=new A();
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(a.getClass().getClassLoader());
B b=new B();
b.print();
}
}
//B.java
public class B{
public void print(){
System.out.println(this.getClass().getClassLoader());
}
public class A{
public static void main(String[] args){
A a=new A();
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(a.getClass().getClassLoader());
B b=new B();
b.print();
}
}
//B.java
public class B{
public void print(){
System.out.println(this.getClass().getClassLoader());
}
1、我們將它放在Classpath中,則打印出
sun.misc.Launcher$AppClassLoader@92e78c
sun.misc.Launcher$AppClassLoader@92e78c
可見都是由AppClassLoader來加載的。
2、我們將其放在%jre%/lib/ext/classes(即ExtClassLoader的加載目錄,其加載/lib/ext中的jar文件或者子目錄classes中的class文件)中。則會打印出:
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$ExtClassLoader
3、我們將A.class放到%jre%/lib/ext/classes中,而將B.class放到classpaht中又會怎麼樣呢?結果是:
sun.misc.Launcher$ExtClassLoader
Exception in thread "main" java.lang.NoClassDefFoundError:B
at A.main(A.java:6)
sun.misc.Launcher$AppClassLoader@92e78c
sun.misc.Launcher$AppClassLoader@92e78c
可見都是由AppClassLoader來加載的。
2、我們將其放在%jre%/lib/ext/classes(即ExtClassLoader的加載目錄,其加載/lib/ext中的jar文件或者子目錄classes中的class文件)中。則會打印出:
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$ExtClassLoader
3、我們將A.class放到%jre%/lib/ext/classes中,而將B.class放到classpaht中又會怎麼樣呢?結果是:
sun.misc.Launcher$ExtClassLoader
Exception in thread "main" java.lang.NoClassDefFoundError:B
at A.main(A.java:6)
怎麼會這樣呢?這其中有一個重要的問題:A類當然是由ExtClassLoader來加載的,B類要由哪個加載呢?B類要由調用它自己的類的類加載器。也就是說,A調用了B,所以B由A的類加載器ExtClassLoader來加載。ExtClassLoader根據委託機制,先拜託Bootstrap加載,Bootstrap沒有找到。然後它再自己尋找B類,還是沒找到,所以拋出異常。
ExtClassLoader不會請求AppClassLoader來加載!你可能會想:這算什麼問題,我把兩個類放到一起不就行了?
ExtClassLoader不會請求AppClassLoader來加載!你可能會想:這算什麼問題,我把兩個類放到一起不就行了?
呵呵,沒這麼簡單。比如JDBC是核心類庫,而各個數據庫的JDBC驅動則是擴展類庫或在classpath中定義的。
所以JDBC由Bootstrap ClassLoader加載,而驅動要由AppClassLoader加載。等等,問題來了,Bootstrap不會請求AppClassLoader加載類啊。
那麼,他們怎麼實現的呢?我就涉及到一個Context ClassLoader的問題,調用Thread.getContextClassLoader。
所以JDBC由Bootstrap ClassLoader加載,而驅動要由AppClassLoader加載。等等,問題來了,Bootstrap不會請求AppClassLoader加載類啊。
那麼,他們怎麼實現的呢?我就涉及到一個Context ClassLoader的問題,調用Thread.getContextClassLoader。
2.自定義ClassLoader.
最簡單的ClassLoader就是繼承ClassLoader類,多態findClass方法。
/**
*@author guojianjun
*@create date Mar 31, 2010
*/
package testMain;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
public class MyClassLoader extends ClassLoader {
private String _classpath;
public MyClassLoader(String classpath) {
this._classpath = classpath;
}
protected Class<?> findClass(String className)
throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);
if (null == clazz) {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
byte[] bytes = baos.toByteArray();
clazz = defineClass(className, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return clazz;
}
private byte[] loadClassBytes(String className)
throws ClassNotFoundException {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
} catch (IOException fnfe) {
throw new ClassNotFoundException(className);
}
}
private String getClassFile(String clasName) {
StringBuffer sb = new StringBuffer(_classpath);
clasName = clasName.replace('.', File.separatorChar) + ".class";
sb.append(File.separator + clasName);
return sb.toString();
}
}
*@author guojianjun
*@create date Mar 31, 2010
*/
package testMain;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
public class MyClassLoader extends ClassLoader {
private String _classpath;
public MyClassLoader(String classpath) {
this._classpath = classpath;
}
protected Class<?> findClass(String className)
throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);
if (null == clazz) {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
byte[] bytes = baos.toByteArray();
clazz = defineClass(className, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return clazz;
}
private byte[] loadClassBytes(String className)
throws ClassNotFoundException {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
} catch (IOException fnfe) {
throw new ClassNotFoundException(className);
}
}
private String getClassFile(String clasName) {
StringBuffer sb = new StringBuffer(_classpath);
clasName = clasName.replace('.', File.separatorChar) + ".class";
sb.append(File.separator + clasName);
return sb.toString();
}
}
測試類:
/**
*@author guojianjun
*@create date Mar 30, 2010
*/
package po;
public class Student {
private String stId = "Anran";
private String stName = "Anran";
private TClass6 stClass = null;
public TClass6 getStClass() {
return stClass;
}
public void setStClass(TClass6 stClass) {
this.stClass = stClass;
}
public String getStId() {
return stId;
}
public void setStId(String stId) {
this.stId = stId;
}
public String getStName() {
return stName;
}
public void setStName(String stName) {
this.stName = stName;
}
}
//test
public class TestLoader {
public static void main(String[] args) throws ClassNotFoundException,
InstantiationException, IllegalAccessException, SecurityException,
NoSuchMethodException, IllegalArgumentException,
InvocationTargetException {
MyClassLoader a = new MyClassLoader("D:\\");
Class test = a.findClass("po.Student");
Object o = test.newInstance();
Method m = test.getDeclaredMethod("getStName", new Class[] {});
System.out.println(m.invoke(o, new Object[] {}));
}
}
*@author guojianjun
*@create date Mar 30, 2010
*/
package po;
public class Student {
private String stId = "Anran";
private String stName = "Anran";
private TClass6 stClass = null;
public TClass6 getStClass() {
return stClass;
}
public void setStClass(TClass6 stClass) {
this.stClass = stClass;
}
public String getStId() {
return stId;
}
public void setStId(String stId) {
this.stId = stId;
}
public String getStName() {
return stName;
}
public void setStName(String stName) {
this.stName = stName;
}
}
//test
public class TestLoader {
public static void main(String[] args) throws ClassNotFoundException,
InstantiationException, IllegalAccessException, SecurityException,
NoSuchMethodException, IllegalArgumentException,
InvocationTargetException {
MyClassLoader a = new MyClassLoader("D:\\");
Class test = a.findClass("po.Student");
Object o = test.newInstance();
Method m = test.getDeclaredMethod("getStName", new Class[] {});
System.out.println(m.invoke(o, new Object[] {}));
}
}