Java程序運行圖:
html
上一篇玩命學JVM(一)—認識JVM和字節碼文件咱們簡單認識了 JVM 和字節碼文件。那JVM是如何使用字節碼文件的呢?從上圖清晰地能夠看到,JVM 經過類加載器完成了這一過程。java
如下是類加載機制的知識框架:數組
接下來咱們對思惟導圖中重難點部分作補充。數據結構
類的加載就是將 .class 文件的二進制數據讀入到內存中,將其放在 JVM 的運行時數據區的方法區內。而後在堆區內建立一個 java.lang.Class 對象,用於封裝類在方法區內的數據結構。框架
雙親委派模型圖以下:
ide
對於「雙親委派模型」,首先須要糾正一點,「雙親」並非說它有「兩個親」。實際上行「雙親委派模型」和「雙」毫無關係,只和「親」有關係。
其實「雙親」是翻譯的一個錯誤,原文出處是「parent」,被翻譯成了「雙親」,在計算機領域更常見的說法是「父節點」。因此若是將「雙親委派模型」改成「父委派模型」,應該更好理解。函數
結合實際的類加載器來講,就是:測試
接下來咱們從源碼上來分析下 雙親委派模型
除Bootstrap ClassLoader
外,其它的類加載器都是ClassLoader
的子類。加載類的方法爲loadClass
,查看源碼可發現,loadClass
在ClassLoader
中有具體的實現,且在各個子類中都沒有被覆蓋。this
先介紹三個重要的函數,對後續的源碼閱讀有幫助:
loadClass
:調用父類加載器的loadClass,加載失敗則調用本身的findClass方法。
findClass
:根據名稱讀取文件存入字節數組。
defineClass
:把一個字節數組轉爲Class對象。.net
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 在JVM中查看類是否已經被加載 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { // 調用父類加載器的 loadClass方法,parent是該類加載器的父類,parent的值可能爲 Application ClassLoader、Extension ClassLoader,當想要繼續往上找 Extension ClassLoader時,因爲Bootstrap ClassLoader是C/C++實現的,因此在java中是Null c = parent.loadClass(name, false); } else { // 尋找 Bootstrap ClassLoader 加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 父加載器開始嘗試加載.class文件,加載成功就返回一個java.lang.Class,加載不成功就拋出一個ClassNotFoundException,給子加載器去加載 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { // 若是要解析這個.class文件的話,就解析一下,解析的做用主要就是將符號引用替換爲直接引用的過程 resolveClass(c); } return c; } }
所謂的雙親委派模型,就是利用了loadClass
只在父類中實現了這一點。
自定義類加載主要有兩種方式:
遵照雙親委派模型:繼承ClassLoader,重寫findClass()方法。
破壞雙親委派模型:繼承ClassLoader,重寫loadClass()方法。 一般咱們推薦採用第一種方法自定義類加載器,最大程度上的遵照雙親委派模型。
咱們看一下實現步驟
(1)建立一個類繼承ClassLoader抽象類
(2)重寫findClass()方法
(3)在findClass()方法中調用defineClass()
第一步,自定義一個實體類Person.java,我把它編譯後的Person.class放在D盤根目錄下:
package com.xrq.classloader; public class Person { private String name; public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return "I am a person, my name is " + name; } }
第二步,自定義一個類加載器,裏面主要是一些IO和NIO的內容,另外注意一下 defineClass方法能夠把二進制流字節組成的文件轉換爲一個java.lang.Class----只要二進制字節流的內容符合Class文件規 範。咱們自定義的MyClassLoader繼承自java.lang.ClassLoader,就像上面說的,只實現findClass方法:
public class MyClassLoader extends ClassLoader { public MyClassLoader() { } public MyClassLoader(ClassLoader parent) { super(parent); } protected Class<?> findClass(String name) throws ClassNotFoundException { File file = getClassFile(name); try { byte[] bytes = getClassBytes(file); Class<?> c = this.defineClass(name, bytes, 0, bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private File getClassFile(String name) { File file = new File("D:/Person.class"); return file; } private byte[] getClassBytes(File file) throws Exception { // 這裏要讀入.class的字節,所以要使用字節流 FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel wbc = Channels.newChannel(baos); ByteBuffer by = ByteBuffer.allocate(1024); while (true) { int i = fc.read(by); if (i == 0 || i == -1) break; by.flip(); wbc.write(by); by.clear(); } fis.close(); return baos.toByteArray(); } }
第三步,Class.forName有一個三個參數的重載方法,能夠指定類加載器,平時咱們使用的Class.forName("XX.XX.XXX")都是使用的系統類加載器Application ClassLoader。寫一個測試類:
public class TestMyClassLoader { public static void main(String[] args) throws Exception { MyClassLoader mcl = new MyClassLoader(); Class<?> c1 = Class.forName("com.xrq.classloader.Person", true, mcl); Object obj = c1.newInstance(); System.out.println(obj); System.out.println(obj.getClass().getClassLoader()); } }
運行結果:
I am a person, my name is null
com.xrq.classloader.MyClassLoader@5d888759
https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc
https://blog.csdn.net/qq_44836294/article/details/105439753