【深入理解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
歡迎關注微信公眾號獲取更多開發技巧幹貨