JAVA學習之ClassLoader

JAVA學習之ClassLoader

前言

最近被 一句話所觸動——**種一棵樹最好的時間是十年前,其次是如今。**因此決定要開始記錄本身的學習之路。html

什麼是類加載?

咱們都知道,每一個.java文件能夠通過javac指令編譯成.class文件,裏面包含着java虛擬機的機器指令。當咱們須要使用一個java類時,虛擬機會加載它的.class文件,建立對應的java對象。將.class調入虛擬機的過程,稱之爲加載。java

loading :加載。經過類的徹底限定名找到.class字節碼文件,同時建立一個對象。mysql

verification:驗證。確保class字節碼文件符合當前虛擬機的要求。c++

preparation:準備。這時候將static修飾的變量進行內存分配,同時設置初始值。sql

resolution:解析。虛擬機將常量池中的符號引用變爲直接引用。數據庫

  • 符號引用:在編譯以後完成的,一個常量並無進行內存分配,也就只能用符號引用。
  • 直接引用:常量會在preparation階段將常量進行內存分配,因而就能夠創建直接的虛擬機內存聯繫,就能夠直接引用。

initialization:初始化。類加載的最後階段。若是這個類有超類,進行超類的初始化,執行類的靜態代碼塊,同時給類的靜態變量賦予初值。前面的preparation階段是分配內存,都只是默認的值,並無被賦予初值。數組

類加載在java中有兩種方式

  • 顯示加載。經過class.fornNme(classname)或者this.getClass().getClassLoader.loadClass(classname)加載。即經過調用類加載器classLoader來完成類的加載
  • 隱式加載。類在須要被使用時,不直接調用ClassLoader,而是虛擬機自動調用ClassLoader加載到內存中。

什麼是ClassLoader?

類加載器的任務是將類的二進制字節流讀入到JVM中,而後變成一個JVM能識別的class對象,同時實例化。因而咱們就能夠分解ClassLoader的任務。ide

  1. 找到類的二進制字節流,並加載進來
  2. 規則化爲JVM能識別的class對象

咱們查看源碼,找到對應解決方案:post

在ClassLoader中,定義了兩個接口:學習

  1. findClass(String name).
  2. defineClass(byte[] b , int off , int len)

findClass用於找到二進制文件並讀入,調用defineClass用字節流變成JVM能識別的class對象,同時實例化class對象。

咱們來看個例子:

protected Class<?> findClass(String name) throws ClassNotFoundException {
	  // 獲取類的字節數組,經過name找到類,若是你對字節碼加密,須要本身解密出來
      byte[] classData = getClassData(name);  
      if (classData == null) {
          throw new ClassNotFoundException();
      } else {
	      //使用defineClass生成class對象
          return defineClass(name, classData, 0, classData.length);
      }
  }

代理模式

提到類加載器,必定得涉及的是委派模式。在JAVA中,ClassLoader存在一下幾類,他們的關係以下:

  • Bootstrap ClassLoader:引導類加載器。採用原生c++實現,用於加載java的核心類(%JAVA_HOME%/lib路徑下的核心類庫或者 -Xbootclasspath指定下的jar包)到內存中。沒有父類

  • Extension ClassLoader:擴展類加載器。java實現,加載/lib/ext目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫。父類加載器爲null。

  • System ClassLoader:它會根據java應用的類路徑(CLASSPATH)來加載類,即java -classpath-D java.class.path 指定路徑下的類庫,也就是咱們常常用到的classpath路徑。通常來講,java應用的類都是由它完成。能夠由ClassLoader.getSystemLoader()方法得到。父類加載器是Extension ClassLoader。

  • Customize ClassLoader:用戶自定義加載器。用於完成用戶自身的特有需求。父類加載器爲System ClassLoader。

    1. 加載不在CLASSPATH路徑的class文件
    2. 加密後的class文件須要用戶自定義ClassLoader來解密後才能被JVM識別。
    3. 熱部署,同一個class文件經過不一樣的類加載器產生不一樣的class對象。兩個類徹底相同不只僅要是同一個class文件,還須要同一個類加載器、同一個JVM。

    代理模式 ,就是像上面圖片所展現那樣,當要加載一個類時,加載器會尋求其父類的幫助,讓父類嘗試去加載這個類。只有當父類失敗後,纔會由本身加載。ClassLoader的loadClass()方法中體現。

    示例代碼:

    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) {
                          //若是找不到,則委託給父類加載器去加載
                          c = parent.loadClass(name, false);
                      } else {
                      //若是沒有父類,則委託給啓動加載器去加載
                          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
                      // 若是都沒有找到,則經過自定義實現的findClass去查找並加載
                      c = findClass(name);
    
                  }
              }
              if (resolve) {//是否須要在加載時進行解析
                  resolveClass(c);
              }
              return c;
          }
      }

    findClass(String name)的相似實現上面有示例,resolveClass()就是完成解析功能。

    URLClassLoader

    URLClassLoader是經常使用的ClassLoader類,其實現了findclass()接口,因此若是自定義時繼承URLClassLoader能夠不用重寫findclass()。ExtClassLoader在代理模式中屬於Extension ClassLoader,而AppClassLoader屬於System ClassLoader。

    線程上下文加載器

    前面咱們提到過BootStrap ClassLoader加載的是%JAVA_HOME%/lib下的核心庫文件,而CLASSPATH路徑下的庫由System ClassLoader加載。但在java語言中,存在這種現象,Java 提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方爲這些接口提供實現,如JDBC、JCE、JNDI等。而SPI是Java的核心庫文件,由 BootStrap ClassLoader加載,第三方實現是放在CLASSPATH路徑下,由System ClassLoader加載。當BootStrap ClassLoader啓用時,須要加載其實現,但本身找不到,又由於代理模式的存在,沒法委託System ClassLoader來加載,因此沒法實現。

    Contex ClassLoader(線程上下文加載器)恰好能夠解決這個問題。

    Contex ClassLoader(線程上下文加載器)是從 JDK 1.2 開始引入的。類 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。若是沒有經過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。

    例子JDBC:

    public class DriverManager {
    	//省略......
        static {
            loadInitialDrivers();
            println("JDBC DriverManager initialized");
        }
    
     private static void loadInitialDrivers() {
         sun.misc.Providers()
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                  //省略沒必要要的代碼......
                }
            });
        }
    public class Driver extends com.mysql.cj.jdbc.Driver {
        public Driver() throws SQLException {
            super();
        }
    
        static {
          //省略
        }
    }
    public static <S> ServiceLoader<S> load(Class<S> service) {
    	 //經過線程上下文類加載器加載
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          return ServiceLoader.load(service, cl);
      }
    //調用
    String url = "jdbc:mysql://localhost:3342/cm-storylocker?characterEncoding=UTF-8";
    // 經過java庫獲取數據庫鏈接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "password");

    咱們能夠看到,當咱們在使用JDBC的時候,會使用有DriverManager類,它的static代碼區引用的ServiceLoader類會完成JDBC實現類的加載。

    如何設計本身的ClassLoader

    給出例子,重寫文件系統加載器

    public class FileSystemClassLoader extends ClassLoader { 
    
       private String rootDir; 
    
       public FileSystemClassLoader(String rootDir) { 
           this.rootDir = rootDir; 
       } 
    
       protected Class<?> findClass(String name) throws ClassNotFoundException { 
           //根據規則獲取字節流數組
           byte[] classData = getClassData(name); 
           if (classData == null) { 
               throw new ClassNotFoundException(); 
           } 
           else { 
               return defineClass(name, classData, 0, classData.length); 
           } 
       } 
     //設定本身的讀取規則
       private byte[] getClassData(String className) { 
           String path = classNameToPath(className); 
           try { 
               InputStream ins = new FileInputStream(path); 
               ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
               int bufferSize = 4096; 
               byte[] buffer = new byte[bufferSize]; 
               int bytesNumRead = 0; 
               while ((bytesNumRead = ins.read(buffer)) != -1) { 
                   baos.write(buffer, 0, bytesNumRead); 
               } 
               return baos.toByteArray(); 
           } catch (IOException e) { 
               e.printStackTrace(); 
           } 
           return null; 
       } 
    
       private String classNameToPath(String className) { 
           return rootDir + File.separatorChar 
                   + className.replace('.', File.separatorChar) + ".class"; 
       } 
    }

    本文學習參考自

    [1] https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#minor1.1

    [2] http://www.javashuo.com/article/p-zoasinon-cr.html

    [3] https://juejin.im/post/5e1aaf626fb9a0301d11ac8e#heading-8

相關文章
相關標籤/搜索