🔎【Java 源碼探索】深入淺出的分析ClassLoader

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"每日一句","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在人生的道路上,即使一切都失去了,只要一息尚存,你就沒有絲毫理由絕望。因爲失去的一切,又可能在新的層次上覆得。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"前提概要","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Java體系中的所有類,必須以【class字節碼文件】必須被裝載到JVM中才能運行,這個裝載工作是由JVM中的類裝載器完成的,類裝載器所做的工作實質是把class字節碼文件從存儲介質(網絡、硬盤、數據庫等多元化方式)讀取到JVM內存中,JVM在加載類的時候,都是通過ClassLoader的loadClass()方法來加載class字節碼的,Java的類加載器使用雙親委派模式進行加載類。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"官方給出ClassLoader功能翻譯爲","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"ClassLoader類是一個抽象類","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * A class loader is an object that is responsible for loading classes. The\n * class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to\n * locate or generate data that constitutes a definition for the class. A\n * typical strategy is to transform the name into a file name and then read a\n * \"class file\" of that name from a file system.\n**/\npublic abstract class ClassLoader\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"大致意思如下(個人補充了完整信息)","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Classloader","attrs":{}},{"type":"text","text":"是一個","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"負責加載classes","attrs":{}},{"type":"text","text":"的對象,ClassLoader類是一個抽象類,給定類的二進制名稱(全限定名),嘗試定位或者產生一個class的元數據信息(class靜態常量池中的元數據)存放到方法區中的運行時常量池以及字符串常量池中。並且創建一個class實例對象指向該方法區內存的地址。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"我們常用的解析方式策略就是將名稱轉換爲物理位置及文件名,然後定位到文件系統等相關媒介中,並且讀取該名稱的“class類文件”","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"前提概要","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"爲了更好的理解類的加載機制,我們來深入研究一下ClassLoader和他的loadClass()方法","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"ClassLoader分類","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Java系統自帶有三個類加載器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Bootstrap ClassLoader","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最頂層的加載類,主要加載核心類庫,%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar等","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Java命令行提供瞭如何擴展bootStrap級別加載class的簡單方法","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"另外需要注意的是可以通過啓動JVM時指定 -Xbootclasspath","attrs":{}},{"type":"text","text":" 和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"路徑來改變Bootstrap ClassLoader的加載目錄","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"語法如下:","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"-Xbootclasspath:","attrs":{}},{"type":"text","text":" ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"完全取代基本核心的Java class 搜索路徑,不常用,否則要重新寫所有Java核心class。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"-Xbootclasspath/a:","attrs":{}},{"type":"text","text":" ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"被指定的文件追加到默認的bootstrap路徑中。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"-Xbootclasspath/p:","attrs":{}},{"type":"text","text":" ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"前綴在覈心class搜索路徑前面。不常用,避免引起不必要的衝突。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"-Dsun.boot.class.path=XXXX","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"分隔符與classpath參數類似,unix使用:號,windows使用;號,這裏以unix爲例","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" java -Xbootclasspath/a:/usrhome/thirdlib.jar: -jar yourJarExe.jar\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Extention ClassLoader(擴展的類加載器)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"加載目錄%JRE_HOME%\\lib\\ext目錄下的jar包和class文件。還可以加載-D java.ext.dirs選項指定的目錄。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以採用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"java.ext.dirs","attrs":{}}],"attrs":{}},{"type":"text","text":" 這個虛擬機參數選項進行設置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Appclass Loader(系統型類加載器)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"也稱爲SystemAppClass加載當前應用的classpath的所有類","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"ClassLoader從繼承關係","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"爲了更好的理解,我們可以查看源碼。看sun.misc.Launcher,它是一個java虛擬機的入口應用。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class Launcher {\n private static Launcher launcher = new Launcher();\n private static String bootClassPath = System.getProperty(\"sun.boot.class.path\");\n public static Launcher getLauncher() {\n return launcher;\n }\n private ClassLoader loader;\n public Launcher() {\n // Create the extension class loader\n ClassLoader extcl;\n try {\n extcl = ExtClassLoader.getExtClassLoader();\n } catch (IOException e) {\n throw new InternalError( \"Could not create extension class loader\", e);\n }\n // Now create the class loader to use to launch the application\n try {\n loader = AppClassLoader.getAppClassLoader(extcl);\n } catch (IOException e) {\n throw new InternalError( \"Could not create application class loader\", e);\n }\n //設置AppClassLoader爲線程上下文類加載器,這個文章後面部分講解\n Thread.currentThread().setContextClassLoader(loader);\n }\n\n /*\n * Returns the class loader used to launch the main application.\n */\n public ClassLoader getClassLoader() {\n return loader;\n }\n\n /*\n * The class loader used for loading installed extensions.\n */\n\n static class ExtClassLoader extends URLClassLoader {}\n\n /**\n * The class loader used for loading from java.class.path.\n * runs in a restricted security context.\n */\n static class AppClassLoader extends URLClassLoader {}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"源碼有精簡,我們可以得到相關的信息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Launcher初始化了ExtClassLoader和AppClassLoader","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Launcher中並沒有看見BootstrapClassLoader","attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"但通過System.getProperty(\"sun.boot.class.path\")得到了字符串bootClassPath,這個應該就是BootstrapClassLoader加載的jar包路徑。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以先代碼測試一下sun.boot.class.path是什麼內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"System.out.println(System.getProperty(\"sun.boot.class.path\"));\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"得到的結果是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"C:\\Program Files\\Java\\jre1.8.0_91\\lib\\resources.jar;\nC:\\Program Files\\Java\\jre1.8.0_91\\lib\\rt.jar;\nC:\\Program Files\\Java\\jre1.8.0_91\\lib\\sunrsasign.jar;\nC:\\Program Files\\Java\\jre1.8.0_91\\lib\\jsse.jar;\nC:\\Program Files\\Java\\jre1.8.0_91\\lib\\jce.jar;\nC:\\Program Files\\Java\\jre1.8.0_91\\lib\\charsets.jar;\nC:\\Program Files\\Java\\jre1.8.0_91\\lib\\jfr.jar;\nC:\\Program Files\\Java\\jre1.8.0_91\\classes\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,這些全是JRE目錄下的jar包或者是class文件。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"ExtClassLoader源碼","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" /*\n * The class loader used for loading installed extensions.\n */\n static class ExtClassLoader extends URLClassLoader {\n\n static {\n ClassLoader.registerAsParallelCapable();\n }\n\n /**\n * create an ExtClassLoader. The ExtClassLoader is created\n * within a context that limits which files it can read\n */\n public static ExtClassLoader getExtClassLoader() throws IOException\n {\n final File[] dirs = getExtDirs();\n try {\n // Prior implementations of this doPrivileged() block supplied\n // aa synthesized ACC via a call to the private method\n // ExtClassLoader.getContext().\n return AccessController.doPrivileged(\n new PrivilegedExceptionAction() {\n public ExtClassLoader run() throws IOException {\n int len = dirs.length;\n for (int i = 0; i < len; i++) {\n MetaIndex.registerDirectory(dirs[i]);\n }\n return new ExtClassLoader(dirs);\n }\n });\n } catch (java.security.PrivilegedActionException e) {\n throw (IOException) e.getException();\n }\n }\n\n private static File[] getExtDirs() {\n String s = System.getProperty(\"java.ext.dirs\");\n File[] dirs;\n if (s != null) {\n StringTokenizer st =\n new StringTokenizer(s, File.pathSeparator);\n int count = st.countTokens();\n dirs = new File[count];\n for (int i = 0; i < count; i++) {\n dirs[i] = new File(st.nextToken());\n }\n } else {\n dirs = new File[0];\n }\n return dirs;\n }\n \n......\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"指定-D java.ext.dirs參數來添加和改變ExtClassLoader的加載路徑。這裏我們通過可以編寫測試代碼","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"System.out.println(System.getProperty(\"java.ext.dirs\"));\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結果如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"C:\\Program Files\\Java\\jre1.8.0_91\\lib\\ext;C:\\Windows\\Sun\\Java\\lib\\ext\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"AppClassLoader源碼","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" /**\n * The class loader used for loading from java.class.path.\n * runs in a restricted security context.\n */\n static class AppClassLoader extends URLClassLoader {\n public static ClassLoader getAppClassLoader(final ClassLoader extcl)\n throws IOException{\n final String s = System.getProperty(\"java.class.path\");\n final File[] path = (s == null) ? new File[0] : getClassPath(s);\n return AccessController.doPrivileged(\n new PrivilegedAction() {\n public AppClassLoader run() {\n URL[] urls =\n (s == null) ? new URL[0] : pathToURLs(path);\n return new AppClassLoader(urls, extcl);\n }\n });\n }\n ......\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到AppClassLoader加載的就是 -Djava.class.path 下的路徑。我們同樣打印它的值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"System.out.println(System.getProperty(\"java.class.path\"));\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結果如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"D:\\workspace\\ClassLoaderDemo\\bin\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個路徑其實就是當前java工程目錄bin,裏面存放的是編譯生成的class文件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"自此我們已經知道了BootstrapClassLoader、ExtClassLoader、AppClassLoader實際是查閱相應的環境屬性(-Xbootclasspath[/a/p]:)sun.boot.class.path、java.ext.dirs和java.class.path來加載資源文件的","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"ClassLoader加載順序","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"我們看到了系統的3種類加載器","attrs":{}},{"type":"text","text":",但我們可能不知道具體哪個先行呢?這與我們的加載方式模型來決定的:上面說過Java使用的是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"雙親委託模型","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java的類加載器查找class和resource時,是通過“委託模式”進行的,它首先判斷這個class是不是已經加載成功,如果沒有的話它並不是自己進行查找,而是先通過父加載器,然後遞歸下去,直到Bootstrap ClassLoader。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果Bootstrap ClassLoader也沒有加載過此class實例,那麼它就會從它指定的路徑中去查找,如果查找成功則返回,如果沒有查找成功則交給子類加載器,也就是ExtClassLoader,這樣類似操作直到終點,也就是我上圖中的紅色箭頭示例。這種機制就叫做雙親委託。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"什麼是雙親委派機制?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個流程可以如下圖所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果當前的類加載器沒有查詢到這個class對象已經加載就請求父加載器(不一定是父類)進行操作,然後以此類推。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體流程描述","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/12/124b6fc6ac4aefc90d83ef43f5c8a4db.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"一個AppClassLoader查找資源時,先看看緩存是否有,緩存有從緩存中獲取,否則委託給父加載器。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"遞歸,重複第1部的操作。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果ExtClassLoader也沒有加載過,則由Bootstrap ClassLoader出面,它首先查找緩存,如果沒有找到的話,就去找自己的規定的路徑下,也就是sun.mic.boot.class下面的路徑。找到就返回,沒有找到,讓子加載器自己去找。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"Bootstrap ClassLoader如果沒有查找成功,則ExtClassLoader自己在java.ext.dirs路徑中去查找,查找成功就返回,查找不成功,再向下讓子加載器找。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路徑下查找。找到就返回。如果沒有找到就讓子類找,如果沒有子類會怎麼樣?拋出各種異常。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的序列,詳細說明了雙親委託的加載流程。我們可以發現委託是從下向上,然後具體查找過程卻是自上至下。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"加載類過程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首先加載類是通過Classloader中的loadClass方法加載,Classloader部分代碼","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"loadClass","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JDK文檔中是這樣寫的,通過指定的全限定類名加載class,它通過同名的loadClass(String,boolean)方法。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"protected Class> loadClass(String name, boolean resolve)\n throws ClassNotFoundException {\n synchronized (getClassLoadingLock(name)) {\n // 首先,檢測是否已經加載\n Class> c = findLoadedClass(name);\n if (c == null) {\n long t0 = System.nanoTime();\n try {\n if (parent != null) {\n //父加載器不爲空則調用父加載器的loadClass\n c = parent.loadClass(name, false);\n } else {\n //父加載器爲空則調用Bootstrap Classloader\n c = findBootstrapClassOrNull(name);\n }\n } catch (ClassNotFoundException e) {\n // ClassNotFoundException thrown if class not found\n // from the non-null parent class loader\n }\n if (c == null) {\n // If still not found, then invoke findClass in order\n // to find the class.\n long t1 = System.nanoTime();\n //父加載器沒有找到,則調用findclass\n c = findClass(name);\n // this is the defining class loader; record the stats\n sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);\n sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);\n sun.misc.PerfCounter.getFindClasses().increment();\n }\n }\n if (resolve) {\n //調用resolveClass()\n resolveClass(c);\n }\n return c;\n }\n }\n\n // 篡改雙親委託\n @Override\n protected Class> loadClass(String className, boolean resolve)\n throws ClassNotFoundException {\n Class> clazz = findLoadedClass(className);\n if (clazz == null) {\n clazz = findClass(className);\n }\n return clazz;\n }\n\n // 主要用於覆蓋此方法去控制雙親委託下的加載實現\n protected final Class> findLoadedClass(String name) {\n ClassLoader loader;\n if (this == BootClassLoader.getInstance())\n loader = null;\n else\n loader = this;\n return VMClassLoader.findLoadedClass(loader, name);\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面是方法原型,一般實現這個方法的步驟是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"執行findLoadedClass(String)去檢測這個class是不是已經加載過了,直接獲取當前的類加載器(系統類加載器)","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"執行父加載器的loadClass方法。如果父加載器爲null,則jvm內置的加載器去替代,也就是Bootstrap ClassLoader。這也解釋了ExtClassLoader的parent爲null,但仍然說Bootstrap ClassLoader是它的父加載器","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果向上委託父加載器沒有加載成功,則通過findClass(String)查找。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果class在上面的步驟中找到了,參數resolve又是true的話,那麼loadClass()又會調用resolveClass(Class)這個方法來生成最終的Class對象。 我們可以從源代碼看出這個步驟。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"另外,要注意的是如果要編寫一個classLoader的子類,也就是自定義一個classloader,建議覆蓋findClass()方法,而不要直接改寫loadClass()方法。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (parent != null) {\n //父加載器不爲空則調用父加載器的loadClass\n c = parent.loadClass(name, false);\n} else {\n //父加載器爲空則調用Bootstrap Classloader\n c = findBootstrapClassOrNull(name);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"前面說過ExtClassLoader的parent爲null,所以它向上委託時,系統會爲它指定Bootstrap ClassLoader。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"synchronized (getClassLoadingLock(name)) 看到這行代碼,我們能知道的是,這是一個同步代碼塊,那麼synchronized的括號中放的應該是一個對象。我們來看getClassLoadingLock(name)方法的作用是什麼","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"protected Object getClassLoadingLock(String className) {\n Object lock = this;\n if (parallelLockMap != null) {\n Object newLock = new Object();\n lock = parallelLockMap.putIfAbsent(className, newLock);\n if (lock == null) {\n lock = newLock;\n }\n }\n return lock;\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上是getClassLoadingLock(name)方法的實現細節,我們看到這裏用到變量parallelLockMap ,根據這個變量的值進行不同的操作,如果這個變量是Null,那麼直接返回this,如果這個屬性不爲Null,那麼就新建一個對象,然後在調用一個putIfAbsent(className, newLock);","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼這個parallelLockMap變量又是哪來的那,我們發現這個變量是ClassLoader類的成員變量:","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private final ConcurrentHashMap parallelLockMap;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個變量的初始化工作在ClassLoader的構造函數中:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private ClassLoader(Void unused, ClassLoader parent) {\n this.parent = parent;\n if (ParallelLoaders.isRegistered(this.getClass())) {\n parallelLockMap = new ConcurrentHashMap<>();\n package2certs = new ConcurrentHashMap<>();\n domains =\n Collections.synchronizedSet(new HashSet());\n assertionLock = new Object();\n } else {\n // no finer-grained lock; lock on the classloader instance\n parallelLockMap = null;\n package2certs = new Hashtable<>();\n domains = new HashSet<>();\n assertionLock = this;\n }\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我們可以看到構造函數根據一個屬性ParallelLoaders的Registered狀態的不同來給parallelLockMap 賦值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在ClassLoader類中包含一個靜態內部類private static class ParallelLoaders,在ClassLoader被加載的時候這個靜態內部類就被初始化。這個靜態內部類的代碼我就不貼了,直接告訴大傢什麼意思,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"sun公司是這麼說的:Encapsulates the set of parallel capable loader types,意識就是說:封裝了並行的可裝載的類型的集合。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面這個說的是不是有點亂,那讓我們來整理一下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,在ClassLoader類中有一個靜態內部類ParallelLoaders,他會指定的類的並行能力,如果當前的加載器被定位爲具有並行能力,那麼他就給parallelLockMap定義,就是new一個 ConcurrentHashMap<>(),那麼這個時候,我們知道如果當前的加載器是具有並行能力的,那麼parallelLockMap就不是Null,這個時候,我們判斷parallelLockMap是不是Null,如果他是null,說明該加載器沒有註冊並行能力,那麼我們沒有必要給他一個加鎖的對象,getClassLoadingLock方法直接返回this,就是當前的加載器的一個實例。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這個parallelLockMap不是null,那就說明該加載器是有並行能力的,那麼就可能有並行情況,那就需要返回一個鎖對象。然後就是創建一個新的Object對象,調用parallelLockMap的putIfAbsent(className, newLock)方法,這個方法的作用是:","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據傳進來的className,檢查該名字是否已經關聯了一個value值,如果已經關聯過value值,那麼直接把他關聯的值返回,如果沒有關聯過值的話,那就把我們傳進來的Object對象作爲value值,className作爲Key值組成一個map返回。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無論putIfAbsent方法的返回值是什麼,都把它賦值給我們剛剛生成的那個Object對象。 這個時候,我們來簡單說明一下getClassLoadingLock(String className)的作用,就是: 爲類的加載操作返回一個鎖對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了向後兼容,這個方法這樣實現:如果當前的classloader對象註冊了並行能力,方法返回一個與指定的名字className相關聯的特定對象,否則,直接返回當前的ClassLoader對象。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"雙親委派機制的作用","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"避免重複加載,當父類加載器已經加載了該類的時候,就沒有必要子類加載器再加載一次了。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首先,類加載器是按照級別層層往下加載的,當下層的加載器去加載某一個類時,有可能上層的加載已經加載過的,比如FrameWork層的加載被BootClassLoader加載過,下層不用再去加載了","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"安全性考慮,防止核心API庫被隨意篡改。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"系統類加載器已經加載過了FrameWork層了類,如果我們自己再寫一個系統級別的類,創建包java.lang,創建一個自己的String類,類加載器去加載這個類覆蓋了原本的java.lang下的String,那麼這個時候使用String整個應用就出問題了","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package java.lang;\n\nclass String {\n\n @NonNull\n @Override\n public String toString() {\n return \"\";\n }\n\n @Override\n public boolean equals(@Nullable Object obj) {\n return false;\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而因爲雙親委派機制,加載這個String類之前,調用parent的BootClassloader,判斷已經加載過String類,這個類其實不會再去找了,解決了被篡改的問題。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"類加載的動態性體現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個應用程序總是由n多個類組成,Java程序啓動時,並不是一次把所有的類全部加載後再運行,它總是先把保證程序運行的基礎類一次性加載到jvm中,其它類等到jvm用到的時候再加載,這樣的好處是節省了內存的開銷,因爲java最早就是爲嵌入式系統而設計的,內存寶貴,這是一種可以理解的機制,而用到時再加載這也是java動態性的一種體現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"類的加載過程","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"裝載:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"通過“類全路徑名”查找並加載類的二進制數據,並且生成class實例對象指向對應的方法區的內存數據結構。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鏈接:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"把類的二進制數據合併到JVM中(主要作爲校驗階段中的class文件格式校驗階段完成存入方法區內存中)","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"驗證:確保被加載類的正確性,確保加載內容不危害虛擬機;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"準備:爲類的靜態變量分配內存,並將其初始化爲默認值,常量直接賦值;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解析:把類中的符號引用轉換爲直接引用;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"初始化:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"直接進行先關的靜態類構造器,實現相關的相關的類屬性和類代碼塊的執行和賦值操作。","attrs":{}}]}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章