【深入理解Java虛擬機 】類加載器的命名空間以及類的卸載
- 2020 年 3 月 2 日
- 筆記
類加載器的命名空間
- 每個類加載器又有一個命名空間,
由其以及其父加載器組成
類加載器的命名空間的作用和影響
- 每個類加載器又有一個命名空間,由其以及其父加載器組成
- 在每個類加載器自己的命名空間中不能出現相同類名的類 (此處值得是類的全名,包含包名)
- 在不同的類命名空間中,可能會出現多個相同的類名的類
如下面的代碼示例中, 首先定義一個類加載器 MyClassLoader
static class MyClassLoader extends ClassLoader { private String classLoaderName; private String classPath; public MyClassLoader(String classPath, String classLoaderName) { super(); // 未指定則默認使用應用類加載器 this.classLoaderName = classLoaderName; this.classPath = classPath; } public MyClassLoader(ClassLoader parent, String classLoaderName) { super(parent); // 顯式的指定父類加載器 this.classLoaderName = classLoaderName; } @Override protected Class<?> findClass(String name) { System.out.println("MyClassLoader.findClass"); byte[] bytes = null; try { bytes = loadClassByte(name); return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { throw new RuntimeException(e); } } private byte[] loadClassByte(String name) throws Exception { name = name.replace(".", "/"); File file = new File(this.classPath + name + ".class"); try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); InputStream fileStream = new FileInputStream(file)) { int ch; while ((ch = fileStream.read()) != -1) { byteStream.write(ch); } return byteStream.toByteArray(); } } }
下面我們嘗試使用此類加載加載一個磁盤的class文件,代碼如下:
public static void main(String[] args) throws ClassNotFoundException { MyClassLoader classLoader = new MyClassLoader("/tmp/", "CustomClassLoader"); Class<?> aClass = classLoader.loadClass("com.zhoutao.classload.ReferenceExample004"); ClassLoader loader = aClass.getClassLoader(); System.out.println(loader); System.out.println("----------------------"); MyClassLoader classLoader2 = new MyClassLoader("/tmp/", "CustomClassLoader"); Class<?> aClass2 = classLoader2.loadClass("com.zhoutao.classload.ReferenceExample004"); ClassLoader loader2 = aClass2.getClassLoader(); System.out.println(loader); }
按照之前的理論,由於 ReferenceExample004
類在classLoader 中已經被加載,那麼在 classLoader2 中將不會被加載,我們看一下輸出的結果
MyClassLoader.findClass com.zhoutao.classload.ReferenceExample010$MyClassLoader@610455d6 ---------------------- MyClassLoader.findClass com.zhoutao.classload.ReferenceExample010$MyClassLoader@60e53b93
總結
可以看到,類加載器的findClass(String) 方法被執行了兩次,這是因為加載該類的類加載器是兩個不同的對象,在文章的開頭提及:每個類加載器又有一個命名空間,由其以及其父加載器組成
上面的兩個類加載器顯然命名空間不一致,所以findClass() 方法會被執行兩次
類的卸載
在內存中的 Class 類沒有引用的時候就會被 JVM 卸載,因為 JVM 自帶的類加載 啟動類加載器,拓展類加載器以及應用類加載器,在三個類加載的實例一直被 JVM 引用,所以JVM 自帶的類加載器加載的類一直被其加載器引用,所以不會被卸載
。
但是自定義的類加載器的實例可以被手動的設置為null,這導致類加載不再被引用,其所加載的 class 類在沒有實例其沒有類加載器引用的情況下就會被卸載,卸載的時間發生在系統垃圾回收的時候
。
類的卸載測試
同類的加載一樣,要觀察到類的卸載,可以通過添加 JVM 參數-XX:+TraceClassUnloading
的方式輸入類的卸載信息。
public static void main(String[] args) throws ClassNotFoundException, InterruptedException { MyClassLoader classLoader = new MyClassLoader("/tmp/", "CustomClassLoader"); Class<?> aClass = classLoader.loadClass("com.zhoutao.classload.ReferenceExample004"); classLoader = null; aClass = null; // 手動觸發GC 觀察類回收的信息 System.gc(); TimeUnit.SECONDS.sleep(10); }
通過控制台可以觀察到類的卸載記錄:
MyClassLoader.findClass [Unloading class com.zhoutao.classload.ReferenceExample004 0x00000007c0061028]
或者通過JVisual 工具觀察到類的卸載過程, 為了便於觀察,適當加長 延時的時間 TimeUnit.SECONDS.sleep(100);
, 終端或者CMD終端 輸入 jvisualvm
命令啟動,啟動測試程序,在 jvisualvm 工具中接連到該線程,可以看到下面的信息:
已卸載的總數為: 1
歡迎關注微信公眾號獲取更多開發技巧幹貨