Java虛擬機中的類加載有三大步驟:,連接,初始化.其中加載是指查找字節流(也就是由Java編譯器生成的class文件)並據此建立類的過程,這中間咱們須要藉助類加載器來查找字節流.java
Java虛擬機提供了3種類加載器,啓動(Bootstrap)類加載器、擴展(Extension)類加載器、應用(Application)類加載器.除了啓動類加載器外,其餘的類加載器都是java.lang.ClassLoader的子類.啓動類加載器由C++語言實現,沒有對應的Java對象,它負責將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數指定的路徑下的jar包加載到內存中.擴展類加載器是指sun.misc.Launcher$ExtClassLoader類,由Java語言實現,是Launcher的靜態內部類,它負責加載<JAVA_HOME>/lib/ext目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫,他的父類加載器是null.應用類加載器是指sun.misc.Launcher$AppClassLoader類,他負責加載應用程序路徑下的類,這裏路徑指java -classpath或-D java.class.path 指定的路徑,他的父類加載器是擴展類加載器.安全
注意這裏面的父子類加載器並非繼承的關係,只是ClassLoader類中的parent屬性.咱們來看Launcher類中建立擴展類加載器的代碼:網絡
public ExtClassLoader(File[] var1) throws IOException { super(getExtURLs(var1), (ClassLoader)null, Launcher.factory); SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); }
這裏設置了其父加載器爲null.jvm
Java虛擬機在加載類時默認採用的是雙親委派機制,即當一個類加載器接收到加載請求時,會將請求轉發到父類加載器,若是父類加載器在路徑下沒有找到該類,纔會交給子類加載器去加載.咱們來看ClassLoader中laodClass方法:測試
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先判斷類是否已加載過,加載過就直接返回 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //有父類加載器,調用父加載器的loadClass 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) { long t1 = System.nanoTime(); //到本身指定類加載路徑下查找是否有class字節碼 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) { resolveClass(c); } return c; } }
經過這種層級咱們能夠避免類的重複加載,當父親已經加載了該類時,就沒有必要子類加載器再加載一次。其次也考慮到安全因素,好比咱們本身寫一個java.lang.String的類,經過雙親委派機制傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會從新加載咱們新寫的java.lang.String,而直接返回已加載過的String.class,這樣保證生成的對象是同一種類型.this
除了jvm自身提供的類加載器,咱們還能夠自定義類加載器,咱們先寫一個Person類加密
public class Person { private int age; private String name; //省略getter/setter方法 }
咱們先看他是由哪一個類加載器加載的.spa
public class TestJava { public static void main(String[] args) throws Exception { Person person = new Person(); System.out.println("person是由" + person.getClass().getClassLoader() + "加載的"); } }
運行結果以下:
3d
咱們把Person.class放置在其餘目錄下code
再運行會發生什麼,在上面的loadClass方法中其實已經有了答案,會拋出ClassNotFoundException,由於在指定路徑下查找不到字節碼.
咱們如今寫一個自定義的類加載器,讓他可以去加載person類,很簡單,咱們只須要繼承ClassLoader並重寫findClass方法,這裏面寫查找字節碼的邏輯.
public class PersonCustomClassLoader extends ClassLoader { private String classPath; public PersonCustomClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } }
咱們來測試一下:
public class TestJava { public static void main(String[] args) throws Exception { PersonCustomClassLoader classLoader = new PersonCustomClassLoader("/home/shenxinjian"); Class<?> pClass = classLoader.loadClass("me.shenxinjian.algorithm.Person"); System.out.println("person是由" + pClass.getClassLoader() + "類加載器加載的"); } }
測試結果以下: