類的加載分爲以下幾個階段:java
咱們首先看一下JVM預約義的三種類加載器,當JVM啓動的時候,Java缺省開始使用以下三種類型的類加載器:安全
(1)啓動(Bootstrap)類加載器:引導類加載器是用C++代碼實現的類加載器,它負責將 <JAVA_HOME>/lib下面的核心類庫 或 -Xbootclasspath選項指定的jar包等 虛擬機識別的類庫 加載到內存中。 app
package com.test; import java.net.URL; public class ClassLoaderTest { public static void main(String[] args) { URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (URL url : urls) { System.out.println(url.toExternalForm()); } } }
輸出以下: jvm
file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/resources.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/rt.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/sunrsasign.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/jsse.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/jce.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/charsets.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/jfr.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/classes
(2)擴展(Extension)類加載器:擴展類加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的,它負責將 <JAVA_HOME >/lib/ext或者由系統變量-Djava.ext.dir指定位置中的類庫 加載到內存中。this
(3)系統(System)類加載器:系統類加載器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的,它負責將 用戶類路徑(java -classpath或-Djava.class.path變量所指的目錄,即當前類所在路徑及其引用的第三方類庫的路徑,如第四節中的問題6所述)下的類庫 加載到內存中。url
AppClassLoader與ExtClassLoader的繼承體系以下: spa
在sun.misc.Launcher類(rt.jar包)中定義了這兩個類加載器,能夠詳細的瞭解Java中相關類加載器的掃描路徑等信息,同時也能看到 建立了兩個加載器的實例,以下:.net
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 { loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError("Could not create application class loader", e); } ... }
ExtClassLoader類的定義以下:線程
/* * The class loader used for loading installed extensions. */ static class ExtClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } /** * 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(); try { // Prior implementations of this doPrivileged() block supplied // aa synthesized ACC via a call to the private method // ExtClassLoader.getContext(). return AccessController.doPrivileged( new PrivilegedExceptionAction<ExtClassLoader>() { public ExtClassLoader run() throws IOException { int len = dirs.length; for (int i = 0; i < len; i++) { MetaIndex.registerDirectory(dirs[i]); } return new ExtClassLoader(dirs); } }); } catch (java.security.PrivilegedActionException e) { throw (IOException) e.getException(); } } /* * 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; } ... }
經過java.security.ClassLoader類(rt.jar包)能夠詳細瞭解雙親委派模式。code
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) { 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 // to find the class. long t1 = System.nanoTime(); 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; } }
先由findLoadedClass
->native方法findLoadedClass0()
,檢查是不是已經加載的類,若是沒加載過且父加載器不爲空,則嘗試由父加載器來加載。
若是父加載器爲空,那麼須要調用findBootstrapClassOrNull
->底層的native方法findBootstrapClass()
來檢查是不是頂級類加載器BootstrapClassLoader
加載過的類。
若是不是,那麼則調用findClass()由子類本身去加載。咱們看一下 URLClassLoader
是怎麼實現邏輯的?
在URLClassLoader#findClass()
中,根據類加載器的加載路徑(包括jar),找到對應的文件,最後調用defineClass
->native方法defineClass1()
將java層的類註冊到jvm中,那麼下次再獲取的時候就能夠從findLoadedClass
方法中找到了。
雙親委派模型有如下幾個優勢:
除了根類加載器、擴展類加載器和系統類加載器外,還能夠定義應用類加載器,其工做流程以下圖所示。
從實現方式來看,能夠分爲兩大類。
一、頂級類加載器bootStrapClassLoader
加載jdk核心類庫
二、用戶代碼由native方法defineClass1
方式加載到jvm中的類,其中jvm中實現類加載的類是SystemDictionary
java在啓動時會調用jvm庫的create_vm
->init_globals
方法時,完成一些重要的初始化工做,其中重要的有
parse_vm_init_args
解析vm參數,vm參數在裏面都可找到。
universe_init
初始化gc策略和,全局堆 和tlab等。tlab是在eden區切出一小塊,(每一個線程均有一個,具體是ThreadLocalAllocBuffer::initialize
,沒看懂是如何計算的,有人說是256k)。
vmSymbols::initialize
建立基礎類型(int,boolean...)的klass句柄。
SystemDictionary::initialize
加載jdk中重要的類
jdk中定義了一些重要的類名爲_well_known_klasses
,_well_known_klasses
的組成能夠由systemDictionary.hpp#WK_KLASSES_DO
枚舉到,涵蓋了Object,String,Class,...一系列重要的類,SystemDictionary::initialize
負責將_well_known_klasses
中的類都加載到jvm中,流程以下:
遍歷頂級加載器的加載路徑,用LazyClassPathEntry::open_stream
尋找到要加載的類的class文件流
調用ClassFileParser::parseClassFile
執行具體的從文件流讀取數據,根據class文件格式標準解析class文件,生成對應的instanceKlass對象。
調用SystemDictionary::find_or_define_instance_class
->SystemDictionary::update_dictionary
->Dictionary::add_klass
將生成的Klass對象存起來。Dictionary
是個hash表實現,使用的也是開鏈法解決hash衝突
除去jvm初始化加載的類,其餘類的加載是由java程序觸發的,調用native方法defineClass1()
。
流程和初始化加載類流程差很少。由java代碼傳入文件流句柄和類名,調用底層代碼SystemDictionary::resolve_from_stream
。其中主要是兩件事:
1.ClassFileParser::parseClassFile
讀取文件生成Klass
2.調用SystemDictionary#define_instance_class()
將類加載到Klass字典中
參考文章:
(1)https://blog.csdn.net/qq_26000415/article/category/9289818