【Java虛擬機器9】類載入器之名稱空間詳解
阿新 • • 發佈:2020-08-12
前言
前面介紹類載入器的時候,介紹了一下名稱空間這個概念。今天就通過一個例子,來詳細瞭解一下【類載入器的名稱空間】。然後通過這個例子,我們可以總結一下雙親委託模型的好處與優點。
例1(不刪除classpath下的class檔案)
首先定義一個MyPerson
package com.jamie.jvmstudy; public class MyPerson { private MyPerson myPerson; public void setMyPerson(Object obj){ this.myPerson = (MyPerson)obj; } }
然後是自定義類載入器
package com.jamie.jvmstudy; import java.io.*; public class CustomizedClassLoader extends ClassLoader { private String classLoaderName; private String path; private String fileExtension = ".class"; public CustomizedClassLoader(String classLoaderName) { super(); this.classLoaderName = classLoaderName; } public CustomizedClassLoader(ClassLoader parent, String classLoaderName) { super(parent); this.classLoaderName = classLoaderName; } @Override public Class<?> findClass(String className) throws ClassNotFoundException { System.out.println("findClass invoked : " + className); System.out.println("class loader name : " + this.classLoaderName); byte[] data = this.loadClassData(className); return this.defineClass(className, data, 0, data.length); } private byte[] loadClassData(String className) { byte[] data = null; className = className.replace(".", "/"); try(InputStream is = new FileInputStream(new File(this.path + className + this.fileExtension)); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { int ch; while(-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return data; } public void setPath(String path) { this.path = path; } }
測試客戶端類
package com.jamie.jvmstudy; import java.lang.reflect.Method; public class TestClassLoaderNameSpace { public static void main(String[] args) throws Exception { CustomizedClassLoader loader1 = new CustomizedClassLoader("loader1"); CustomizedClassLoader loader2 = new CustomizedClassLoader("loader2"); Class<?> clazz1 = loader1.loadClass("com.jamie.jvmstudy.MyPerson"); Class<?> clazz2 = loader2.loadClass("com.jamie.jvmstudy.MyPerson"); System.out.println("clazz1的classLoader是" + clazz1.getClassLoader()); System.out.println("clazz2的classLoader是" + clazz2.getClassLoader()); System.out.println( clazz1 == clazz2); Object object1 = clazz1.newInstance(); Object object2 = clazz2.newInstance(); Method method = clazz1.getMethod("setMyPerson", Object.class); method.invoke(object1, object2); } }
結果:
clazz1的classLoader是sun.misc.Launcher$AppClassLoader@14dad5dc
clazz2的classLoader是sun.misc.Launcher$AppClassLoader@14dad5dc
true
說明:
同一個類載入器(本例是應用類載入器)載入同一個類,得到的class物件是相同的。
例2(基於例1修改,刪除classpath下的class檔案)
操作
為自定義類載入器設定path,然後編譯成功後,刪除掉classpath下面的MyPerson.class檔案,把編譯出的MyPerson.class檔案移動到D:/temp資料夾裡面。
public class TestClassLoaderNameSpace {
public static void main(String[] args) throws Exception {
CustomizedClassLoader loader1 = new CustomizedClassLoader("loader1");
CustomizedClassLoader loader2 = new CustomizedClassLoader("loader2");
loader1.setPath("D:/temp/");
loader2.setPath("D:/temp/");
Class<?> clazz1 = loader1.loadClass("com.jamie.jvmstudy.MyPerson");
Class<?> clazz2 = loader2.loadClass("com.jamie.jvmstudy.MyPerson");
System.out.println("clazz1的classLoader是" + clazz1.getClassLoader());
System.out.println("clazz2的classLoader是" + clazz2.getClassLoader());
System.out.println( clazz1 == clazz2);
Object object1 = clazz1.newInstance();
Object object2 = clazz2.newInstance();
Method method = clazz1.getMethod("setMyPerson", Object.class);
method.invoke(object1, object2);
}
}
程式碼執行結果
findClass invoked : com.jamie.jvmstudy.MyPerson
class loader name : loader1
findClass invoked : com.jamie.jvmstudy.MyPerson
class loader name : loader2
clazz1的classLoader是com.jamie.jvmstudy.CustomizedClassLoader@677327b6
clazz2的classLoader是com.jamie.jvmstudy.CustomizedClassLoader@7f31245a
false
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:67)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.jamie.jvmstudy.TestClassLoaderNameSpace.main(TestClassLoaderNameSpace.java:23)
... 5 more
Caused by: java.lang.ClassCastException: com.jamie.jvmstudy.MyPerson cannot be cast to com.jamie.jvmstudy.MyPerson
at com.jamie.jvmstudy.MyPerson.setMyPerson(MyPerson.java:8)
... 10 more
結論
loader1和loader2分別載入了MyPerson.class,分別給MyPerson.class分配了記憶體空間,如下圖:
這2個class物件雖然在檔案系統是來自於同一個class檔案,但是由於他們是被自定義類載入器載入的,並且這2個自定義類載入器是同級的,沒有父子關係。所以雙親委派模型中,他們是看不到對方的名稱空間的。
所以clazz1 == clazz2
的結果為false
自己畫了一個簡圖:
回顧名稱空間概念
- 每個類載入器都有自己的名稱空間。名稱空間由該載入器和所有父載入器所載入的類組成。(請結合下圖一起看,想明白)
- 在同一個名稱空間中,不會出現類的完整名字(包括類的包名)相同的兩個類。
- 在不同的名稱空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類。
雙親委託模型的好處
- 確保Java核心類庫的安全:所有的Java應用都至少會引用java.lang.Object類,也就是說在執行期,java.lang.Object類會被載入到Java虛擬機器當中;如果這個載入過程是由Java應用自己的類載入器所完成的,那麼可能會在JVM中存在多個版本的java.lang.Object類,而且這些類還是不相容的、相互不可見的(因為名稱空間的原因)。藉助雙親委託機制,Java核心類庫中的類的載入工作都是由啟動類載入器來統一完成的,從而確保了Java應用所使用的都是同一個版本的Java核心類庫,他們之間是互相相容的。
- 確保Java核心類庫提供的類不會被自定義的類所替代。
- 不同的類載入器可以為相同名稱(binary name)的類建立額外的名稱空間。相同名稱的類可以並存在Java虛擬機器中,只需要用不同的類載入器來加他們即可,不同類載入器所載入的類是不相容的,這就相當於在Java虛擬機器內部建立了一個又一個相互隔離的Java類空間。