類加載器


類加載器可以加載類,這些類被HotSpot加載后,都以Klass對象表示。涉及到的主要的類加載器有啟動類加載器/引導類加載器(Bootstrap ClassLoader)、擴展類加載器(Extension ClassLoader)和應用類加載器/系統類加載器(Application ClassLoader)。

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

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

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

源代碼位置:hotspot/src/share/vm/classfile/classLoader.hpp

class ClassLoader::AllStatic {
private:
   // First entry in linked list of ClassPathEntry instances
   // ClassPathEntry類指針,ClassPathEntry用於表示單個classpath路徑,
   // 所有的ClassPathEntry實例以鏈表的形式關聯起來,_first_entry表示鏈表的第一個實例
   static ClassPathEntry* _first_entry;
   // Last entry in linked list of ClassPathEntry instances
   // 表示鏈表的最后一個實例
   static ClassPathEntry* _last_entry;
   // Hash table used to keep track of loaded packages
   // 用於保存已經加載過的包名
   static PackageHashtable* _package_hash_table;

   // ...
   // 加載類
   static instanceKlassHandle load_classfile(Symbol* h_name,TRAPS);
   // 設置加載路徑
   static void setup_bootstrap_search_path();
public:
   // 初始化類加載器
   static void initialize();
   // ...
}

通過_first_entry鏈表保存這個類加載器可以加載的一些類路徑。在虛擬機啟動時會通過調用ClassLoader::setup_bootstrap_search_path()函數來設置。

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,false,CHECK_(h));
    ...
    // 調用ClassLoader的add_package方法,把當前類的包名加入到_package_hash_table中
    if (add_package(name, classpath_index, THREAD)) {
       h = result;
    }
  } 

  return h;
}

每個類加載器都對應着一個ClassLoaderData對象,通過ClassLoaderData::the_null_class_loader_data()函數獲取引導類加載器對應的ClassLoaderData對象。

調用add_package()將已經解析過的類進行保存,避免重復加載解析。邏輯實現並不復雜,這里不在介紹。

parseClassFile()方法就是解析Class文件中的類、字段、常量池等信息,然后轉換為C++內部的對等表示,如類元信息存儲在InstanceKlass實例中,常量池信息存儲在ConstantPool中,部分的C++對等實現(類模型)在之前已經介紹過,這里不再介紹。后續會詳細介紹parseClassFile()方法解析Class文件的過程。

2、擴展類加載器 

擴展類加載器用Java語言編寫,由sun.misc.Launcher$ExtClassLoader類實現,負責將 <JAVA_HOME >/lib/ext目錄或者由系統變量-Djava.ext.dir所指定的目錄中的類庫加載到內存中。 

擴展類加載器ExtClassLoader的實現如下: 

源代碼位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.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時,java.lang.ClassLoader類中實現的loadClass()方法在加載一個類時將會調用findBootstrapClassOrNull()方法,而這個方法最終會調用C++實現的ClassLoader類的相關方法進行類加載。

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

系統類加載器用Java語言編寫,由sun.misc.Launcher$AppClassLoader類實現,負責將系統環境變量-classpath、-cp或系統屬性java.class.path指定的路徑下的類庫加載到內存中。

擴展類加載器AppClassLoader的實現如下:

源代碼位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.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類的構造函數中實例化系統類加載器AppClassLoader時,會調用getAppClassLoader()方法獲取系統類加載器,傳入的參數是一個擴展類加載器ExtClassLoader實例,這樣系統類加載器的父加載器就變成了擴展類加載器(與父加載器並非繼承關系)。用戶自定義的無參類加載器的父類加載器默認就是AppClassloader類加載器。

4、構造類加載器實例

HotSpot在啟動過程中會在<JAVA_HOME>/lib/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()方法的實現,如下: 

源代碼位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java
private static ClassLoader scl;

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; } } 

如上方法及變量定義在java.lang.ClassLoader類中。

在getSystemClassLoader()函數中調用Launcerh.getLauncher()方法獲取Launcher實例,實例通過靜態變量launcher來保存,靜態變量的定義如下:

源代碼位置:openjdk/jdk/src/share/classes/sum/misc/Launcher.java

private static Launcher launcher = new Launcher();

調用l.getClassLoader()方法獲取類加載器實例,如下:

源代碼位置:openjdk/jdk/src/share/classes/sum/misc/Launcher.java

public ClassLoader getClassLoader() {
     return loader; // 返回的loader就是Launcher類的loader,也就是應用類加載器AppClassLoader
}

auncher()類的構造函數如下:

源代碼位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.java

private ClassLoader loader;

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); 
}

如上方法及變量定義在sumn.misc.Lanucher類中。

可以看到有對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源碼剖析系列文章!

  

參考文章:

(1)類加載器的實現

(2)類的預加載 

(3)Java類的加載

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM