Java類加載器詳解

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.

雙親委派機制

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,這樣保證生成的對象是同一種類型.

自定義類加載器

除了jvm自身提供的類加載器,我們還可以自定義類加載器,我們先寫一個Person類

public class Person {

  private int age;

  private String name;

  //省略getter/setter方法
}

我們先看他是由哪個類加載器加載的.

public class TestJava {

  public static void main(String[] args) throws Exception {
    Person person = new Person();
    System.out.println("person是由" + person.getClass().getClassLoader() + "加載的");
  }
}

運行結果如下:
圖片描述

我們把Person.class放置在其他目錄下

圖片描述

再運行會發生什麼,在上面的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() + "類加載器加載的");

  }
}

測試結果如下:
圖片描述

編寫自定義類加載器的意義

  • 當class文件不在classPath路徑下,如上面那種情況,默認系統類加載器無法找到該class文件,在這種情況下我們需要實現一個自定義的classLoader來加載特定路徑下的class文件來生成class對象。
  • 當一個class文件是通過網絡傳輸並且可能會進行相應的加密操作時,需要先對class文件進行相應的解密後再加載到JVM內存中,這種情況下也需要編寫自定義的ClassLoader並實現相應的邏輯
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章