c++ 类数组_《深入java虚拟机》读书笔记类加载
概述
類加載機制是指虛擬機將描述類的數據從Class文件中加載到內存,并進行數據驗證、解析、初始化等過程,最后形成可以直接被虛擬機使用的java類型。在java語言中類的加載、鏈接、初始化等過程并不是在編譯時期完成,而是在運行時期才進行的,這樣做的好處在于可以為語言提供了動態擴展的特性(可以在運行期間動態接收二進制的字節碼文件解析運行),壞處在于增加了性能的開銷。
類加載過程
類在jvm中的整個生命周期包括:
加載(loading)-->驗證(verification)-->準備(preparation)-->解析(resolution)-->初始化(initialization)-->使用(using)-->卸載(unloading)
其中驗證、準備、解析并稱為鏈接階段。并且加載、驗證、準備、解析、初始化階段的開始時機是確定的,當然這里不需要等待前一個完成才執行后一個。而解析過程則不一定了,因為Java語言可能會存在運行時動態解析。
加載
加載過程實際上就是通過二進制字節流生成Class對象的過程,整個過程可以分為以下階段: 1. 通過類的全限定名來獲取定義該類二進制字節流,這里的字節流并不要求一定是從Class文件獲取,可以從壓縮包(jar,war)、其他文件生成(jsp)、網絡、計算生成(proxy)等等途徑獲得。 2. 將這個字節流所表示的靜態存儲結構轉換為方法去的動態運行時數據結構。數據存儲格式由虛擬機自行實現。 3. 在內存中實例化一個java.lang.Class對象,作為方法區中該類的數據訪問入口。在HotSpot虛擬機中,Class對象在JDK1.7前是存儲在方法區中,在JDK1.7之后Class實例存放在堆中。
在加載過程中的獲取二進制字節流階段既可以使用JDK提供了類加載器進行加載,也可以使用我們自定義的類加載器加載。但是如果是加載一個數組類就有些不一樣了。
數組類本身并不通過類加載器加載,其由虛擬機直接創建,但是數組中承載的對象還是由類加載器加載:
如果數組類承載的對象時引用類型那么就遞歸(數組可以嵌套)的調用加載過程對類進行加載,同時該數組將在加載被承載對象的類加載器的類名稱空間中被標識。如果數組中的內容并不是引用類型(基本數據類型),那么Java虛擬機將會將該數組標記為與引導類加載器關聯。
驗證
驗證是鏈接過程的開始,這一階段的目的是為了保證Class文件的字節流中包含的信息符合虛擬機的要求,并且不會危害虛擬機的安全。
在加載一節我們說過二進制的字節流并不一定必須是通過java代碼編譯而來,統一通過多種形式,甚至可以直接使用十六進制數據構造。所以我們必須要對數據進行驗證,否則隨意的數據可能導致虛擬機崩潰。在java虛擬機規范中對數據的約束和規范規則較多,大致可以分為4種: 1. 文件格式驗證:主要驗證字節流是否符合Class文件規范以及能否被當前版本的虛擬機處理,包括魔數驗證,版本號等等。目的是為了保證輸入的字節流能夠正確的解析和存儲于方法區內,在格式上符合描述一個java類型信息的要求。通過驗證后,會將字節流信息存儲于內存中的方法區中。 2. 元數據驗證:對字節碼描述的信息進行語義分析,保證不存在不符合java語言規范的元數據。如該類是否有父類,父類是否繼承了不允許繼承的類等等。 3. 字節碼驗證:通過數據流和控制流來確定程序語義是否是合法的、符合邏輯的。該階段是對類的方法體進行校驗。例如保證指令的跳轉不會跳到方法體外的字節碼指令上,保證類型轉換時有效地等等。但是即使通過了字節碼驗證也并不能表示程序就是絕對無措的。因為我們的檢驗程序的程序本身是可能有錯或不足的。 4. 符號引用驗證:該驗證是在解析階段進行的(類加載的過程可能是相互交叉的)。符號引用驗證的目的是為了確保解析動作能正常執行。如果無法通過符號引用驗證,像java.lang.NoSuchMethodError等都是在該階段拋出的。
需要注意的是該校驗并不是每一次都需要的,假設我們的程序在測試環境下多次測試都是正常的,那么我們在生產環境下可以通過-Xverify:none參數來關閉大部分的驗證措施,用以提高虛擬機的類加載時間。
準備
準備階段是正式為類變量分配內存和設置類變量初始值的階段,這些變量所需的內存都是你在方法區進行分配。
需要注意的是這里分配內存的僅僅是類變量(static)而不包括實例變量。實例變量內存分配和賦初值是在對對象實例化階段進行的。同時這里的賦初值也并不是設置我們在程序中定義好的值,而是零值,比如int類型的零值為0,應用類型零值為null。
但是如果是constantValue(同時被final和static進行修飾)的值則是在該階段進行賦值的,其他類型正式賦值是在初始化階段進行的。
解析
解析是虛擬機將常量池中的符號引用替換為直接引用的過程。解析主要的對類,接口,字段,類方法,接口方法,方法類型等符引用進行。
- 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任意形式的字面量,只要使用時能夠無歧義的定位到目標即可。
- 直接引用:直接引用可以是直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。
初始化
初始化過程實際上就是對變量賦值(不是賦初值)的過程。包含所有類變量的賦值以及靜態代碼語句塊的執行代碼,包括對父類的初始化。
類加載器
在前面的加載過程中有說到“通過類的全限定名來獲取該類的二進制字節碼流”,這一過程的實現就是通過類加載器完成的。
類與類加載器
對于任意的一個類,都需要由該類本身和加載該類的類加載器來確定其在Java虛擬機中的唯一性,每一個類加載器都有一個獨立的類名稱空間。這句話的意思就是要判斷兩個類是否一樣,不能僅僅比較兩個類是否通過同一個Class文件生成的,假如兩個類通過同一個Class文件生成但是各自加載他們的類加載器不一樣,那么這兩個類也是不相等的,在使用equals方法和instanceof關鍵字等時都遵循這個原則。
幾種類加載器
這里介紹幾種已經內置的類加載器(都是針對于HotSpot vm): - BootStrap ClassLoader(啟動類加載器):該類加載器負責加載/lib目錄和由-Xbootclasspath參數指定的路徑下的類庫。但是并不是說把任意類庫放在lib目錄下都會被加載,必須是虛擬機內定義好的名字的類庫。同時在HotSpot VM中BootStrap ClassLoader是由C++實現,所以在java程序中我們無法獲取到該類加載器的實例,如果我們自定義的類加載器中需要用到BootStrap ClassLoader的話可以直接使用null代替。 - Extension ClassLoader(擴展類加載器):該類加載器負責加載/lib/ext目錄下或者由java.ext.dirs變量指定的路徑下的類庫,該類加載器我們可以直接使用。 - Application ClassLoader(應用類加載器):該類加載器負責加載用戶路徑(ClassPath)下的類庫。該類加載器也是默認的類加載器。
我們也可以繼承ClassLoader類并重寫findClass方法進行自定義類加載器。
雙親委派模型(Parents Delegation Model)
上面介紹的不同類加載器之間也并不是各自獨立運作的,其相互之間存在著一些關系。
像上圖這種層級關系被稱為雙親委派模型,雙親委派模型是如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當父加載器在自己的搜索范圍內找不到指定的類時,子加載器才會嘗試自己去加載。實際上雙親委派模型的并不是指每一個類加載器都有兩個父加載器,parents是指可以有多個父加載器,當然也可以只有一個。這里的翻譯有點怪怪的。
雙親委派模型的好處在于通過不同層次的類加載器加載的類先天的帶有一個層次關系,保證同一個類不會被多次加載,例如類java.lang.Object,它由啟動類加載器加載。雙親委派模型保證任何類加載器收到的對java.lang.Object的加載請求,最終都是委派給處于模型最頂端的啟動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。
雙親委派模型的實現:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {Class<?> c = findLoadedClass(name);//如果該類沒有被加載則通過類加載器加載//已經被加載就返回if (c == null) {long t0 = System.nanoTime();try {//判斷父加載器是否為空 為空表示使用BootStrap ClassLoader加載if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//父加載器無法加載該類 下面有當前類加載器自己加載}if (c == null) {long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}雙親委派模型并不是一個強制性的約束,我們的實現理論上可以不遵循該模型理論。如果有必要的話可能會對雙親委派模型進行破壞(讓父類加載器主動將類加載行為交給子類進行或者直接由當前類加載器加載不交給父類),比如JNDI,OSGI等。
如何破壞雙親委托
在自定義類加載器中我們一般是繼承ClassLoader類并重寫findClass方法,這樣findClass方法會調用loadClass就實現了雙親委托模型(loadClass方法會自動的先去找父類加載器)。
如果要破壞雙親委托模型的話可以直接實現loadClass方法即可。
總結
以上是生活随笔為你收集整理的c++ 类数组_《深入java虚拟机》读书笔记类加载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue和layui哪个更好用_幕布和Mi
- 下一篇: 电脑销售渠道_“新冠”影响下,平板电脑市