當前位置:
首頁 > 知識 > 我的面試準備過程——JVM相關

我的面試準備過程——JVM相關


Jvm 相關

類載入機制類載入概念

類載入指的是將類的.class文件中的二進位數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的載入的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的介面。

載入.class文件的方式

  • 從本地系統中直接載入

  • 通過網路下載.class文件

  • 從zip,jar等歸檔文件中載入.class文件

  • 從專有資料庫中提取.class文件

  • 將Java源文件動態編譯為.class文件

類的生命周期

(略)

類載入器

其中,

  1. 父類載入器並不是通過繼承關係來實現的,而是採用組合實現的;

  2. bootstrap ClassLoader是用C++實現的;

  3. 對JVM來說,類載入器分為啟動類載入器bootstrap ClassLoader和其他載入器

  4. 對開發者來說,分為啟動類載入器、擴展類載入器、應用程序類載入器、自定義類載入器

啟動類載入器:Bootstrap ClassLoader,負責載入存放在JDKjrelib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader載入)。啟動類載入器是無法被Java程序直接引用的。

擴展類載入器:Extension ClassLoader,該載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入DKjrelibext目錄中,或者由java.ext.dirs系統變數指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類載入器。

應用程序類載入器:Application ClassLoader,該類載入器由sun.misc.Launcher$AppClassLoader來實現,它負責載入用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類載入器,如果應用程序中沒有自定義過自己的類載入器,一般情況下這個就是程序中默認的類載入器。

JVM類載入機制
  • 全盤負責,當一個類載入器負責載入某個Class時,該Class所依賴的和引用的其他Class也將由該類載入器負責載入,除非顯示使用另外一個類載入器來載入

  • 父類委託,先讓父類載入器試圖載入該類,只有在父類載入器無法載入該類時才嘗試從自己的類路徑中載入該類

  • 緩存機制,緩存機制將會保證所有載入過的Class都會被緩存,當程序中需要使用某個Class時,類載入器先從緩存區尋找該Class,只有緩存區不存在,系統才會讀取該類對應的二進位數據,並將其轉換成Class對象,存入緩存區。這就是為什麼修改了Class後,必須重啟JVM,程序的修改才會生效。

類的載入

類載入有三種方式

  1. 命令行啟動應用時候由JVM初始化載入

  2. 通過Class.forName方法動態載入

  3. 通過ClassLoader.loadClass方法動態載入

雙親委派模型

雙親委派模型的工作流程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委託給父載入器去完成,依次向上,因此,所有的類載入請求最終都應該被傳遞到頂層的啟動類載入器中,只有當父載入器在它的搜索範圍中沒有找到所需的類時,即無法完成該載入,子載入器才會嘗試自己去載入該類。

工作流程:

  1. 當AppClassLoader載入一個class時,它首先不會自己去嘗試載入這個類,而是把類載入請求委派給父類載入器ExtClassLoader去完成。

  2. 當ExtClassLoader載入一個class時,它首先也不會自己去嘗試載入這個類,而是把類載入請求委派給BootStrapClassLoader去完成。

  3. 如果BootStrapClassLoader載入失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),會使用ExtClassLoader來嘗試載入;

  4. 若ExtClassLoader也載入失敗,則會使用AppClassLoader來載入,如果AppClassLoader也載入失敗,則會報出異常ClassNotFoundException。

JVM內存結構

JVM內存結構主要有三大塊:堆內存、方法區和棧。堆內存存放對象以及數組的數據,堆內存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內存又被分成三部分,Eden空間、From Survivor空間、To Survivor空間,默認情況下年輕代按照8:1:1的比例來分配。

方法區存儲類信息、常量、靜態變數等數據,是線程共享的區域,為與Java堆區分,方法區還有一個別名Non-Heap(非堆),方法區存放類的信息(包括類名、方法、欄位)、靜態變數、編譯器編譯後的代碼。

棧又分為java虛擬機棧和本地方法棧主要用於方法的執行。

對於大多數應用來說,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裡分配內存。如果在堆中沒有內存完成實例分配,並且堆也無法再擴展時,將會拋出OutOfMemoryError異常。

方法區

方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機載入的類信息、常量、靜態變數、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。

程序計數器(Program Counter Register)

程序計數器是一塊較小的內存空間,它的作用可以看做是當前線程所執行的位元組碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

由於Java虛擬機的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)只會執行一條線程中的指令。因此,為了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域為「線程私有」的內存。

如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機位元組碼指令的地址;如果正在執行的是Natvie方法,這個計數器值則為空(Undefined)。

此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。

JVM棧(JVM Stacks)

與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變數表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。

局部變數表存放了編譯期可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同於對象本身,根據不同的虛擬機實現,它可能是一個指向對象起始地址的引用指針,也可能指向一個代表對象的句柄或者其他與此對象相關的位置)和returnAddress類型(指向了一條位元組碼指令的地址)。

其中64位長度的long和double類型的數據會佔用2個局部變數空間(Slot),其餘的數據類型只佔用1個。局部變數表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變數空間是完全確定的,在方法運行期間不會改變局部變數表的大小。

在Java虛擬機規範中,對這個區域規定了兩種異常狀況:如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常;如果虛擬機棧可以動態擴展(當前大部分的Java虛擬機都可動態擴展,只不過Java虛擬機規範中也允許固定長度的虛擬機棧),當擴展時無法申請到足夠的內存時會拋出OutOfMemoryError異常。

本地方法棧(Native Method Stacks)

本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧為虛擬機執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機使用到的Native方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。

GC演算法對象存活判斷

判斷對象是否存活一般有兩種方式:

  1. 引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,無法解決對象相互循環引用的問題。

  2. 可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。不可達對象。其中,在Java語言中,GC Roots包括:

    • 虛擬機棧中引用的對象。

    • 方法區中類靜態屬性實體引用的對象。

    • 方法區中常量引用的對象。

    • 本地方法棧中JNI引用的對象。

標記 -清除演算法

「標記-清除」(Mark-Sweep)演算法,如它的名字一樣,演算法分為「標記」和「清除」兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象。之所以說它是最基礎的收集演算法,是因為後續的收集演算法都是基於這種思路並對其缺點進行改進而得到的。它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

複製演算法

「複製」(Copying)的收集演算法,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種演算法的代價是將內存縮小為原來的一半,持續複製長生存期的對象則導致效率降低。

標記-壓縮演算法

複製收集演算法在對象存活率較高時就要執行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種演算法。根據老年代的特點,有人提出了另外一種「標記-整理」(Mark-Compact)演算法,標記過程仍然與「標記-清除」演算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存

分代收集演算法

GC分代的基本假設:絕大部分對象的生命周期都非常短暫,存活時間短。「分代收集」(Generational Collection)演算法,把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集演算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製演算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記-清理」或「標記-整理」演算法來進行回收。

收集器

(略)

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 科技優家 的精彩文章:

Vulkan Tutorial 23 Descriptor layout and buffer
Akka(10): 分布式運算:集群-Cluster
詳解 RAC 中各種IP和監聽的意義

TAG:科技優家 |

您可能感興趣

我的面試準備過程:ubuntu 使用過程記錄
在備考過程中,ACT考試各部分都要注意些什麼?
「HPV相關疾病免疫預防專家共識」制定過程中的思考
NMO的恢復過程
實時記錄我備考AFP考試的過程
簡化客戶與開發者合作過程,VR管理系統Varwin開始公測
Google面試官抖出自己的面試題,有詳細的分解過程
面試過程中,面試官的哪些舉動在暗示著你已經通過面試?
你真的了解HTTPS的加密過程嗎
她進SM試鏡的過程,李秀滿親自發話,SM特別錄用!
CPU的製作過程詳解
SQL Server服務遠程過程調用失敗解決
BEYOND激光軟體鏈接WYSIWYG方法!過程詳細!
帶你回顧 CONVERSE#真的有故事#系列活動全過程…
PL/SQL存儲過程常見錯誤及測試方法
測試過程與用戶使用過程
PG全裝備獨角獸製作過程篇
短視頻APP開發過程中iOS端的一些經驗分享
NIBS湯楠組同期兩篇Dev Cell,封面論文報道肺泡發育過程及其調控機制
PS使用過程中你不可錯過的幾個插件