ClassLoader 加載機(jī)制
1. Java Class Loading Mechanism首先當(dāng)編譯一個(gè)Java文件時(shí),編譯器就會(huì)在生成的字節(jié)碼中內(nèi)置一個(gè)public,static,final的class字段,該字段屬于java.lang.Class類型,該class字段使用點(diǎn)來訪問,所以可以有:
java.lang.Class clazz = MyClass.class
當(dāng)class被JVM加載,就不再加載相同的class。class在JVM中通過(ClassLoader,Package,ClassName)來唯一決定。ClassLoader指定了一個(gè)class的scope,這意味著如果兩個(gè)相同的包下面的class被不同的ClassLoader加載,它們是不一樣的,并且不是type-compatible的。
JVM中所有的ClassLoader(bootstrap ClassLoader除外)都是直接或間接繼承于java.lang.ClassLoader抽象類,并且人為邏輯上指定了parent-child關(guān)系,實(shí)現(xiàn)上child不一定繼承于parent,我們也可以通過繼承它來實(shí)現(xiàn)自己的ClassLoader。
JVM ClassLoder架構(gòu),從上到下依次為parent-child關(guān)系:
- Bootstrap ClassLoader - 啟動(dòng)類加載器,主要負(fù)責(zé)加載核心Java類如java.lang.Object和其他運(yùn)行時(shí)所需class,位于JRE/lib目錄下或-Xbootclasspath指定的目錄。我們不知道過多的關(guān)于Bootstrap ClassLoader的細(xì)節(jié),因?yàn)樗且粋€(gè)native的實(shí)現(xiàn),不是Java實(shí)現(xiàn),所以不同JVMs的Bootstrap ClassLoader的行為也不盡相同。調(diào)用java.lang.String.getClassLoder() 返回null。
- sun.misc.ExtClassLoader - 擴(kuò)展類加載器,負(fù)責(zé)加載JRE/lib/ext目錄及-Djava.ext.dirs指定目錄。
- sun.misc.AppClassLoader - 應(yīng)用類加載器,負(fù)責(zé)加載java.class.path目錄
另外,還有一些其他的ClassLoader如:
java.net.URLClassLoader,java.security.SecureClassLoader,java.rmi.server.RMIClassLoader,sun.applet.AppletClassLoader- 用戶還可以自己繼承java.lang.ClassLoader來實(shí)現(xiàn)自己的ClassLoader,用來動(dòng)態(tài)加載class文件。
ClassLoader特性:
- 每個(gè)ClassLoader維護(hù)一份自己的命名空間,同一個(gè)ClassLoader命名空間不能加載兩個(gè)同名的類。
- 為實(shí)現(xiàn)Java安全沙箱模型,默認(rèn)采用parent-child加載鏈結(jié)構(gòu),除Bootstrap ClassLoader沒有parent外,每個(gè)ClassLoader都有一個(gè)邏輯上的parent,就是加載這個(gè)ClassLoader的ClassLoader,因?yàn)镃lassLoader本身也是一個(gè)類,直接或間接的繼承java.lang.ClassLoader抽象類。
java.lang.Thread中包含一個(gè)public的方法public ClassLoader getContextClassLoader(),它返回某一線程相關(guān)的ClassLoader,該ClassLoader是線程的創(chuàng)建者提供的用來加載線程中運(yùn)行的classes和資源的。如果沒有顯式的設(shè)置其ClassLoader,默認(rèn)是parent線程的Context ClassLoader。Java默認(rèn)的線程上下文加載器是AppClassLoader。
ClassLoader工作原理:
了解ClassLoader工作原理,先來看一個(gè)ClassLoader類簡(jiǎn)化版的loadClass()方法源碼
1 protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 Class c = findLoadedClass(name);
7 if (c == null) {
8 long t0 = System.nanoTime();
9 try {
10 if (parent != null) {
11 c = parent.loadClass(name, false);
12 } else {
13 c = findBootstrapClassOrNull(name);
14 }
15 } catch (ClassNotFoundException e) {
16 // ClassNotFoundException thrown if class not found
17 // from the non-null parent class loader
18 }
19
20 if (c == null) {
21 // If still not found, then invoke findClass in order
22 // to find the class.
24 c = findClass(name);
25 }
26 }
27 if (resolve) {
28 resolveClass(c);
29 }
30 return c;
31 }
32 }
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 Class c = findLoadedClass(name);
7 if (c == null) {
8 long t0 = System.nanoTime();
9 try {
10 if (parent != null) {
11 c = parent.loadClass(name, false);
12 } else {
13 c = findBootstrapClassOrNull(name);
14 }
15 } catch (ClassNotFoundException e) {
16 // ClassNotFoundException thrown if class not found
17 // from the non-null parent class loader
18 }
19
20 if (c == null) {
21 // If still not found, then invoke findClass in order
22 // to find the class.
24 c = findClass(name);
25 }
26 }
27 if (resolve) {
28 resolveClass(c);
29 }
30 return c;
31 }
32 }
首先查看該class是否已被加載,如果已被加載則直接返回,否則調(diào)用parent的loadClass來加載,如果parent是null代表是Bootstrap ClassLoader,則有Bootstrap ClassLoader來加載,如果都未加載成功,最后由該ClassLoader自己加載。這種parent-child委派模型,保證了惡意的替換Java核心類不會(huì)發(fā)生,因?yàn)槿绻x了一個(gè)惡意java.lang.String,它首先會(huì)被JVM的Bootstrap ClassLoader加載自己JRE/lib下的,而不會(huì)加載惡意的。另外,Java允許同一package下的類可以訪問受保護(hù)成員的訪問權(quán)限,如定義一個(gè)java.lang.Bad,但是因?yàn)閖ava.lang.String由Bootstrap ClassLoader加載而java.lang.Bad由AppClassLoader加載,不是同一ClassLoader加載,仍不能訪問。
2. Hotswap - 熱部署
即不重啟JVM,直接替換class。因?yàn)镃lassLoader特性,同一個(gè)ClassLoader命名空間不能加載兩個(gè)同名的類,所以在不重啟JVM的情況下,只能通過新的ClassLoader來重新load新的class。
1 public static void main(String[] args) throws InterruptedException, MalformedURLException {
2 IExample oldExample = new Example();
3 oldExample.plus();
4 System.out.println(oldExample.getCount());
5
6 Hotswap hotswap = new Hotswap();
7 while (true) {
8 IExample newExample = hotswap.swap(oldExample);
9 String message = newExample.message();
10 int count = newExample.plus();
11 System.out.println(message.concat(" : " + count));
12 oldExample = newExample;
13 Thread.sleep(5000);
14 }
15 }
16
2 IExample oldExample = new Example();
3 oldExample.plus();
4 System.out.println(oldExample.getCount());
5
6 Hotswap hotswap = new Hotswap();
7 while (true) {
8 IExample newExample = hotswap.swap(oldExample);
9 String message = newExample.message();
10 int count = newExample.plus();
11 System.out.println(message.concat(" : " + count));
12 oldExample = newExample;
13 Thread.sleep(5000);
14 }
15 }
16
利用hotswap替換就的Example,每5秒鐘輪詢一次,swap方法實(shí)現(xiàn)如下:
1 private IExample swap(IExample old) {
2 try {
3 String sourceFile = srcPath().concat("Example.java");
4 if (isChanged(sourceFile)) {
5 comiple(sourceFile, classPath());
6 MyClassLoader classLoader = new MyClassLoader(new URL[]{new URL("file:"+classPath())});
7 Class<?> clazz = classLoader.loadClass("Example");
8 System.out.println(IExample.class.getClassLoader());
9 IExample exampleInstance = ((IExample) clazz.newInstance()).copy(old);
10 System.out.println(exampleInstance.getClass().getClassLoader());
11 return exampleInstance;
12 }
13 } catch ...
24 return old;
25 }
2 try {
3 String sourceFile = srcPath().concat("Example.java");
4 if (isChanged(sourceFile)) {
5 comiple(sourceFile, classPath());
6 MyClassLoader classLoader = new MyClassLoader(new URL[]{new URL("file:"+classPath())});
7 Class<?> clazz = classLoader.loadClass("Example");
8 System.out.println(IExample.class.getClassLoader());
9 IExample exampleInstance = ((IExample) clazz.newInstance()).copy(old);
10 System.out.println(exampleInstance.getClass().getClassLoader());
11 return exampleInstance;
12 }
13 } catch ...
24 return old;
25 }
這里必須將exampleInstance轉(zhuǎn)型為IExample接口而不是Exmaple,否則會(huì)拋出ClassCastExecption,這是因?yàn)閟wap方法所在類Hotswap是有AppClassLoader加載的,而且加載Hotswap的同時(shí)會(huì)加載該類引用的Exmaple的symbol link,而Example是MyClassLoader加載的,不同的ClassLoader加載的類之間直接用會(huì)拋出ClassCastException, 在本例中ClassLoader實(shí)現(xiàn)如下:
1 public class MyClassLoader extends URLClassLoader {
2
3 public MyClassLoader(URL[] urls) {
4 super(urls);
5 }
6
7 @Override
8 public Class<?> loadClass(String name) throws ClassNotFoundException {
9 if ("Example".equals(name)) {
10 return findClass(name);
11 }
12 return super.loadClass(name);
13 }
14 }
2
3 public MyClassLoader(URL[] urls) {
4 super(urls);
5 }
6
7 @Override
8 public Class<?> loadClass(String name) throws ClassNotFoundException {
9 if ("Example".equals(name)) {
10 return findClass(name);
11 }
12 return super.loadClass(name);
13 }
14 }
而對(duì)IExample我們還是調(diào)用super的loadClass方法,該方法實(shí)現(xiàn)仍是JVM的parent-child委派方式,因此最終由AppClassLoader加載,加載Hotswap時(shí)加載的symbol link也是由AppClassLoader加載的,因此能夠成功。
此外再熱部署時(shí),被替換的類的所有引用及狀態(tài)都要遷移到新的類上,本例中只是很簡(jiǎn)單的調(diào)用copy函數(shù)遷移了count的狀態(tài)。
Tomcat的jsp熱部署機(jī)制就是基于ClassLoader實(shí)現(xiàn)的,對(duì)于其類的熱部署機(jī)制是通過修改內(nèi)存中的class字節(jié)碼實(shí)現(xiàn)的。
Resource:
posted on 2012-09-08 17:58 *** 閱讀(635) 評(píng)論(0) 編輯 收藏