ClassLoader(一)- 介紹

本文源代碼在Githubhtml

本文僅爲我的筆記,不該做爲權威參考。java

原文git

什麼是ClassLoader

javadoc ClassLoadergithub

A class loader is an object that is responsible for loading classes.
...
Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class.
A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.

簡單來講:bootstrap

  1. ClassLoader是一個負責加載Class的對象。
  2. 給ClassLoader一個類名(需符合Java語言規範),那麼它就應該嘗試定位,或者生成包含該類定義的數據。
  3. 一個典型的定位策略是把類名轉換成class文件名,而後從文件系統裏讀取這個class文件。

三種ClassLoader實現

講到bootstrap class loader就不得不說三種常見的ClassLoader實現。api

執行下面代碼會看到三種類型的ClassLoader實現:數組

import com.sun.javafx.util.Logging;
import java.util.ArrayList;
public class PrintClassLoader {
  public static void main(String[] args) {
    System.out.println("Classloader for ArrayList: " + ArrayList.class.getClassLoader());
    System.out.println("Classloader for Logging: " + Logging.class.getClassLoader());
    System.out.println("Classloader for this class: " + PrintClassLoader.class.getClassLoader());
  }
}

結果以下:安全

Classloader for ArrayList: null
Classloader for Logging: sun.misc.Launcher$ExtClassLoader@5e2de80c
Classloader for this class: sun.misc.Launcher$AppClassLoader@18b4aac2
  • Bootstrap class loader。bootstrap class loader是native code寫的。它是全部ClassLoader的祖先,它是頂級ClassLoader。它負責加載JDK的內部類型,通常來講就是位於$JAVA_HOME/jre/lib下的核心庫和rt.jar
  • Extension class loader。即Extension class loader,負責加載Java核心類的擴展,加載$JAVA_HOME/lib/ext目錄和System Property java.ext.dirs所指定目錄下的類(見Java Extension Mechanism Architecture)。
  • System class loader,又稱Application class loader。它的parent class loader是extension class loader(能夠從sun.misc.Launcher的構造函數裏看到),負責加載CLASSPATH環境變量、-classpath/-cp啓動參數指定路徑下的類。

類的ClassLoader

每一個Class對象引用了當初加載本身的ClassLoader(javadoc ClassLoader):oracle

Every Class object contains a reference to the ClassLoader that defined it.

其實Class對象的getClassLoader()方法就可以獲得這個ClassLoader,而且說了若是該方法返回空,則說明此Class對象是被bootstrap class loader加載的,見getClassLoader() javadocjvm

Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.

數組類的ClassLoader

Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.

簡單來講說了三點:

  1. 數組也是類,可是它的Class對象不是由ClassLoader建立的,而是由Java runtime根據須要自動建立的。
  2. 數組的getClassLoader()的結果同其元素類型的ClassLoader
  3. 若是元素是基礎類型,則數組類沒有ClassLoader

下面是一段實驗代碼:

import com.sun.javafx.util.Logging;
public class PrintArrayClassLoader {
  public static void main(String[] args) {
    System.out.println("ClassLoader for int[]: " + new int[0].getClass().getClassLoader());
    System.out.println("ClassLoader for string[]: " + new String[0].getClass().getClassLoader());
    System.out.println("ClassLoader for Logging[]: " + new Logging[0].getClass().getClassLoader());
    System.out.println("ClassLoader for this class[]: " + new PrintArrayClassLoader[0].getClass().getClassLoader());
  }
}

獲得的結果以下,符合上面的說法:

ClassLoader for int[]: null
ClassLoader for string[]: null
ClassLoader for Logging[]: sun.misc.Launcher$ExtClassLoader@5e2de80c
ClassLoader for this class[]: sun.misc.Launcher$AppClassLoader@18b4aac2

那若是是二維數組會怎樣呢?下面是實驗代碼:

import com.sun.javafx.util.Logging;
public class PrintArrayArrayClassLoader {
  public static void main(String[] args) {
    System.out.println("ClassLoader for int[][]: " + new int[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for string[][]: " + new String[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for Logging[][]: " + new Logging[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for this class[][]: " + new PrintArrayClassLoader[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for this Object[][] of this class[]: " + new Object[][]{new PrintArrayArrayClassLoader[0]}.getClass().getClassLoader());
  }
}

結果是:

ClassLoader for int[][]: null
ClassLoader for string[][]: null
ClassLoader for Logging[][]: sun.misc.Launcher$ExtClassLoader@5e2de80c
ClassLoader for this class[][]: sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader for this Object[][] of this class[]: null

注意第四行的結果,咱們構建了一個Object[][],裏面放的是PrintArrayArrayClassLoader[],但結果依然是null。因此:

  1. 二維數組的ClassLoader和其定義的類型(元素類型)的ClassLoader相同。
  2. 與其實際內部存放的類型無關。

ClassLoader類的ClassLoader

ClassLoader自己也是類,那麼是誰加載它們的呢?實際上ClassLoader類的ClassLoader就是bootstrap class loader。下面是實驗代碼:

import com.sun.javafx.util.Logging;
public class PrintClassLoaderClassLoader {
  public static void main(String[] args) {
    // Launcher$ExtClassLoader
    System.out.println("ClassLoader for Logging's ClassLoader: " + Logging.class.getClassLoader().getClass().getClassLoader());
    // Launcher$AppClassLoader
    System.out.println("ClassLoader for this class's ClassLoader: " + PrintClassLoaderClassLoader.class.getClassLoader().getClass().getClassLoader());
    // 自定義ClassLoader
    System.out.println("ClassLoader for custom ClassLoader: " + DummyClassLoader.class.getClassLoader().getClass().getClassLoader());
  }
  public static class DummyClassLoader extends ClassLoader {
  }
}

結果是:

ClassLoader for Logging's ClassLoader: null
ClassLoader for this class's ClassLoader: null
ClassLoader for custom ClassLoader: null

ClassLoader解決了什麼問題

簡單來講ClassLoader就是解決類加載問題的,固然這是一句廢話。JDK裏的ClassLoader是一個抽象類,這樣作的目的是可以讓應用開發者定製本身的ClassLoader實現(好比添加解密/加密)、動態插入字節碼等,我認爲這纔是ClassLoader存在的最大意義。

ClassLoader的工做原理

仍是看javadoc的說法

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

簡單來講:

  1. ClassLoader使用委託模型(國內廣泛稱之爲雙親委派模型)查找Class或Resource。
  2. 每一個 ClassLoader 實例都有一個parent ClassLoader。
  3. 當要查找Class或者Resource的時候,遞歸委託給parent,若是parent找不到,纔會本身找。舉例說明:若是ClassLoader層級關係是這樣A->B->C,若是被查找Class只能被A找到,那麼過程是A-delegate->B-delegate->C(not found)->B(not found)->A(found)。
  4. JVM有一個內置的頂級ClassLoader,叫作bootstrap class loader,它沒有parent,它是老祖宗。

ContextClassLoader

ClassLoader的委託模型存在這麼一個問題:子ClassLoader可以看見父ClassLoader所加載的類,而父ClassLoader看不到子ClassLoader所加載的類。

這個問題出如今Java提供的SPI上,簡單舉例說明:

  1. Java核心庫提供了SPI A
  2. 嘗試提供了本身的實現 B
  3. SPI A嘗試查找實現B,結果找不到

這是由於B通常都是在Classpath中的,它是被System class loader加載的,而SPI A是在覈心庫裏的,它是被bootstrap class loader加載的,而bootstrap class loader是頂級ClassLoader,它不能向下委託給System class loader,因此SPI A是找不到實現B的。

這個時候能夠經過java.lang.Thread#getContextClassLoader()java.lang.Thread#setContextClassLoader來讓SPI A加載到B。

爲什麼SPI A不直接使用System class loader來加載呢?我想這是由於若是寫死了System class loader那就缺乏靈活性的關係吧。

Class的惟一性

若是一個類被一個ClassLoader加載兩次,那麼兩次的結果應該是一致的,而且這個加載過程是線程安全的,見ClassLoader.java源碼:

protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
    // First, check if the class has already been loaded
    Class<?> c = findLoadedClass(name);
    if (c == null) {
      // ...
      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
        // to find the class.
        // ...
        c = findClass(name);

        // ...
      }
    }
    // ...
    return c;
  }
}

若是一個類被兩個不一樣的ClassLoader加載會怎樣呢?看下面代碼:

// 把這個項目打包而後放到/tmp目錄下
public class ClassUniqueness {

  public static void main(String[] args) throws Exception {
    Class<?> fooClass1 = Class.forName("me.chanjar.javarelearn.classloader.ClassUniqueness");
    System.out.println("1st ClassUniqueness's ClassLoader: " + fooClass1.getClassLoader());

    // 故意將parent class loader設置爲null,不然就是SystemClassLoader(即ApplicationClassLoader)
    URLClassLoader ucl = new URLClassLoader(new URL[] { new URL("file:///tmp/classloader.jar") }, null);
    Class<?> fooClass2 = ucl.loadClass("me.chanjar.javarelearn.classloader.ClassUniqueness");
    System.out.println("2nd ClassUniqueness's ClassLoader: " + fooClass2.getClassLoader());

    System.out.println("Two ClassUniqueness class equals? " + fooClass1.equals(fooClass2));
  }

}

運行結果是:

1st ClassUniqueness's ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
2nd ClassUniqueness's ClassLoader: java.net.URLClassLoader@66d3c617
Two ClassUniqueness class equals? false```

觀察到兩點:

  1. 雖然是同一個類,可是加載它們的ClassLoader不一樣。
  2. 雖然是同一個類,可是它們並不相等。

由此能夠得出結論:一個Class的惟一性不只僅是其全限定名(Fully-qualified-name),而是由【加載其的ClassLoader + 其全限定名】聯合保證惟一。

這種機制對於解決諸如類衝突問題很是有用,類衝突問題就是在運行時存在同一個類的兩個不一樣版本,同時代碼裏又都須要使用這兩個不一樣版本的類。解決這個問題的思路就是使用不一樣的ClassLoader加載這兩個版本的類。事實上OSGi或者Web容器就是這樣作的(它們不是嚴格遵守委託模型,而是先本身找,找不到了再委託給parent ClassLoader)。

參考文檔

廣告

clipboard.png

相關文章
相關標籤/搜索