1. 程式人生 > 實用技巧 >類載入器

類載入器

類載入器可以載入類,這些類被HotSpot載入後,都以Klass物件表示。涉及到的主要的類載入器有啟動類載入器/引導類載入器(Bootstrap ClassLoader)、擴充套件類載入器(Extension ClassLoader)和應用類載入器/系統類載入器(Application ClassLoader)。

1、引導類載入器/啟動類載入器

引導類載入器由ClassLoader類實現,這個ClassLoader類是用C++語言來實現的,它負責將<JAVA_HOME>/lib目錄、-Xbootclasspath選項指定的目錄或系統屬性sun.boot.class.path指定的目錄下的核心類庫載入到記憶體中。

用C++語言定義的類載入器及重要的函式如下:

class ClassLoader::AllStatic {
private:
 ...
     // 載入類
     static instanceKlassHandle load_classfile(Symbol* h_name,TRAPS);
     // 設定載入路徑
     static void setup_bootstrap_search_path();

    public:
     // 初始化類載入器
     static void initialize();
    ...
}

load_classfile()方法可以根據類名載入類,具體實現如下:

原始碼位置:openjdk/hotspot/src/share/vm/classfile/classLoader.cpp
instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) {
  // 獲取類名
  const char* class_name = h_name->as_C_string();
  ....
 
  stringStream st;
  st.print_raw(h_name->as_utf8());
  st.print_raw(".class");
  // 獲取檔名
  const char* file_name = st.as_string();
  ClassLoaderExt::Context context(class_name, file_name, THREAD);
 
  // ClassFileStream表示Class檔案的位元組流
  ClassFileStream* stream = NULL;
  int classpath_index = 0;
  ClassPathEntry* e = NULL;
  instanceKlassHandle h;
  {
    //從第一個ClassPathEntry開始遍歷所有的ClassPathEntry
    e = _first_entry;
    while (e != NULL) {
      stream = e->open_stream(file_name, CHECK_NULL);
      // 如果檢查返回false則返回null,check方法預設返回true
      if (!context.check(stream, classpath_index)) {
        return h; // NULL
      }
      // 如果找到目標檔案則跳出迴圈
      if (stream != NULL) {
        break;
      }
      e = e->next();
      ++classpath_index;
    }
  }
  //如果找到了目標class檔案
  if (stream != NULL) {
    // 構建一個ClassFileParser例項
    ClassFileParser parser(stream);
    // 構建一個ClassLoaderData例項
    ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data();
    Handle protection_domain;
    TempNewSymbol parsed_name = NULL;
    // 解析並載入class檔案,注意此時並未開始連結
    instanceKlassHandle result = parser.parseClassFile(h_name,
                                                       loader_data,
                                                       protection_domain,
                                                       parsed_name,
                                                       context.should_verify(classpath_index),
                                                       THREAD);
    ...
    // 呼叫ClassLoader的add_package方法,把當前類的包名加入到_package_hash_table中
    h = context.record_result(classpath_index, e, result, THREAD);
  } 
  ...
  return h;
}

parseClassFile()方法就是解析Class檔案中的類、欄位、常量池等資訊,然後轉換為C++內部的對等表示,如類元資訊儲存在InstanceKlass例項中,常量池資訊儲存在ConstantPool中,部分的C++對等實現(類模型)在之前已經介紹過,這裡不再介紹。後續會詳細介紹parseClassFile()方法解析Class檔案的過程。

2、擴充套件類載入器

擴充套件類載入器由ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現,負責將<JAVA_HOME >/lib/ext目錄或者由系統變數-Djava.ext.dir所指定的目錄中的類庫載入到記憶體中。

用Java語言編寫的擴充套件類載入器的實現如下:

原始碼位置:
static class ExtClassLoader extends URLClassLoader {
 
    /**
     * create an ExtClassLoader. The ExtClassLoader is created
     * within a context that limits which files it can read
     */
    public static ExtClassLoader getExtClassLoader() throws IOException
    {
        final File[] dirs = getExtDirs(); // 獲取要載入類的載入路徑
 
        ...
        return new ExtClassLoader(dirs); // 例項化擴充套件類載入器
        ...
    }
 
    /*
     * Creates a new ExtClassLoader for the specified directories.
     */
    public ExtClassLoader(File[] dirs) throws IOException {
        super(getExtURLs(dirs), null, factory); // parent傳遞的引數為null,所以並不是引導類載入器
    }
 
    private static File[] getExtDirs() {
        String s = System.getProperty("java.ext.dirs");
        File[] dirs;
        if (s != null) {
            StringTokenizer st = new StringTokenizer(s, File.pathSeparator);
            int count = st.countTokens();
            dirs = new File[count];
            for (int i = 0; i < count; i++) {
                dirs[i] = new File(st.nextToken());
            }
        } else {
            dirs = new File[0];
        }
        return dirs;
    } 
    ...
}

ExtClassLoader類的建構函式中在呼叫父類的建構函式時,傳遞的第2個引數的值為null,這個值最終會賦值給parent欄位,所以後面將會講到,當這個欄位的值為null時,ClassLoader類中實現的loadClass()方法會呼叫findBootstrapClassOrNull()方法載入類,最終會呼叫C++實現的ClassLoader類的相關方法。

3、系統類載入器/應用類載入器

系統類載入器由AppClassLoader(sun.misc.Launcher$AppClassLoader)實現,負責將由系統環境變數-classpath、-cp或系統屬性java.class.path指定的路徑下的類庫載入到記憶體中。

用Java語言編寫的擴充套件類載入器的實現如下:

原始碼位置:
static class AppClassLoader extends URLClassLoader {
 
        public static ClassLoader getAppClassLoader(final ClassLoader extcl)throws IOException {
            final String s = System.getProperty("java.class.path");
            final File[] path = (s == null) ? new File[0] : getClassPath(s);
 
            ...
            return new AppClassLoader(urls, extcl);
        }
 
        /*
         * Creates a new AppClassLoader
         */
        AppClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent, factory); // parent引數是一個擴充套件類載入器例項
        }
 
        /**
         * Override loadClass so we can checkPackageAccess.
         */
        public Class loadClass(String name, boolean resolve)throws ClassNotFoundException{
            ...
            return (super.loadClass(name, resolve));
        }
        
        ...
}

在Launcher類的建構函式中例項化系統類載入器時,會呼叫getAppClassLoader()方法獲取系統類載入器,傳入的引數是一個擴充套件類載入器例項,這樣系統類載入器的父載入器就變成了擴充套件類載入器。使用者自定義的無參加載器的父類載入器預設就是AppClassloader載入器。

4、構造類載入器例項

HotSpot在啟動過程中會在rt.jar包裡面的sun.misc.Launcher類中完成擴充套件類載入器和系統類載入器的例項化,也會進行引導類載入器的初始化,也就是呼叫C++語言編寫的ClassLoader類的initialize()方法。

HotSpot在初始化時,會初始化一個重要的變數,定義如下:

原始碼位置:hotspot/src/share/vm/classfile/systemDictionary.cpp

oop  SystemDictionary::_java_system_loader  =  NULL;

這個屬性儲存系統類載入器例項,HotSpot在載入主類時會使用這個類載入器載入主類。屬性在compute_java_system_loader()方法中初始化,呼叫鏈路如下:

JavaMain()                                      java.c	
InitializeJVM()                                 java.c
JNI_CreateJavaVM()                              jni.cpp	
Threads::create_vm()                            thread.cpp
SystemDictionary::compute_java_system_loader()  systemDictionary.cpp

方法的實現如下:

void SystemDictionary::compute_java_system_loader(TRAPS) {
  KlassHandle system_klass(THREAD, WK_KLASS(ClassLoader_klass));
  JavaValue result(T_OBJECT);
// 呼叫java.lang.ClassLoader類的getSystemClassLoader()方法 JavaCalls::call_static(&result, // 呼叫Java靜態方法的返回值儲存在result中 KlassHandle(THREAD, WK_KLASS(ClassLoader_klass)), // 呼叫的目標類為java.lang.ClassLoader vmSymbols::getSystemClassLoader_name(), // 呼叫目標類中的目標方法為getSystemClassLoader vmSymbols::void_classloader_signature(), // 呼叫目標方法的方法簽名 CHECK); // 獲取呼叫getSystemClassLoader()方法的結果並儲存到_java_system_loader屬性中 _java_system_loader = (oop)result.get_jobject(); // 初始化屬性為系統類載入器/應用類載入器/AppClassLoader }

通過JavaClass::call_static()方法呼叫java.lang.ClassLoader類的getSystemClassLoader()方法。JavaClass::call_static()方法非常重要,它是HotSpot呼叫Java靜態方法的API,後面傳經詳細介紹。

下面看一下getSystemClassLoader()方法的實現,如下: 

public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        return scl;
}
 
private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); // 獲取Launcher例項
            if (l != null) {
                scl = l.getClassLoader();
                // ...               
            }
            sclSet = true;
        }
} 

呼叫Launcerh.getLauncher()方法獲取Launcher例項,例項通過靜態變數launcher來儲存,靜態變數的定義如下:

private static Launcher launcher = new Launcher();

呼叫l.getClassLoader()方法獲取類載入器例項,如下:

public ClassLoader getClassLoader() {
        return loader; // 返回的loader就是Launcher類的loader,也就是系統類載入器AppClassLoader
}

Launcher()類的建構函式如下:

public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;
        try { 
            // 首先建立了擴充套件類載入器
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError("Could not create extension class loader", e);
        }
 
        // Now create the class loader to use to launch the application
        try { 
            // 以ExtClassloader作為父載入器建立了AppClassLoader
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError("Could not create application class loader", e);
        }
 
        // Also set the context class loader for the primordial thread. 
        // 預設執行緒上下文載入器為AppClassloader
        Thread.currentThread().setContextClassLoader(loader); 
}

可以看到有對ExtClassLoader與AppClassLoader例項建立的邏輯,這樣HotSpot就可以通過_java_system_loader屬性獲取AppClassLoader例項,通過AppClassLoader例項中的parent屬性使用ExtClassLoader。 

相關文章的連結如下:

1、在Ubuntu 16.04上編譯OpenJDK8的原始碼

2、除錯HotSpot原始碼

3、HotSpot專案結構 

4、HotSpot的啟動過程

5、HotSpot二分模型(1)

6、HotSpot的類模型(2)

7、HotSpot的類模型(3)

8、HotSpot的類模型(4)

9、HotSpot的物件模型(5)

10、HotSpot的物件模型(6)

11、操作控制代碼Handle(7)

12、控制代碼Handle的釋放(8)

關注公眾號,有HotSpot原始碼剖析系列文章!