個人面試準備過程--JVM相關

Jvm 相關

 類加載機制

本段參考 http://www.importnew.com/2374...html

類加載概念

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

加載.class文件的方式程序員

  • 從本地系統中直接加載算法

  • 經過網絡下載.class文件數據庫

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

  • 從專有數據庫中提取.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內存結構

本節參考 http://www.importnew.com/2374...

http://ifeve.com/under-the-ho...

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算法

本節參考 https://my.oschina.net/hosee/...

對象存活判斷

判斷對象是否存活通常有兩種方式:

  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堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。而老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記-清理」或「標記-整理」算法來進行回收。

收集器

(略)

相關文章
相關標籤/搜索