HotSpot JVM基本原理(一)

最近學習了JVM的相關知識,主要是關於HosSpot的,這裏大致整理一下。


1.HotSpot JVM的結構

wKioL1PyO4uym9mxAAGc8JnqaLY552.jpg

上圖描述 HotSpot的大致結構,從圖中我們可以看出JVM的大致流程是把一個class文件通過類加載器加載進系統,然後把放到不同的區域,通過編譯器編譯。


2.Heap結構

做過java的都知道,java的堆分爲3個代,我們稱之爲分代管理。

wKiom1PyO0-CNaQCAADpvh-fbFo916.jpg

上圖可以看出三個代分別是年輕代,老年代和永久代。年輕代。這裏有一種假設,假設對象初次建立的時候大部分都創建在年輕代。當對象年齡到達年輕代的上限,默認值是15,如果經過15次垃圾回收他還在新生代,那麼他就會晉升成老年代。

那麼什麼是永久代呢?

永久代的意思就是這部分的內容默認情況下是不受垃圾回收的控制的,例如本地接口調用的數據存放在永久代,當那一個類被類加載器加載進來的時候,他裏面的數據是存在永久代的,這些類的卸載以及本地接口的內存釋放是非常嚴格的,針對這種情況,一般會放在永久代,垃圾回收器不做管理。當然也有例外。

由圖還可以看出新生代分爲3個區,Eden,S0,S1,S代表survival存活。這些會在後面提高。


3.內存回收和TLAB

wKioL1PyPYfw2I-aAAESzmE2VIk027.jpg

首先上一張圖,假設圖中整個是一個內存區,例如新生代的Eden區,那麼Eden是怎麼分區的呢?先只看TLAB1,這代表一個線程在對象申請時狀態的快照,也就是說一個線程初次申請的時候會在內存中劃一塊區域(深色代表申請了),它會有一個指針指向這個對象下一個連續空間的起始點(free_list),如果下一次要申請內存,通過指針向後挪下一次申請的尺寸量,這種機制就叫做Bump The Pointer。


其次因爲一個對象要分配,那麼一塊區域就屬於這個對象所擁有,其他對象是不能夠進來的,如果把整個區域當成一個對象的話,那麼申請這塊內存的時候,就必須讓內存塊之前進行同步,這個同步的過程是通過鎖來實現的,也就是說在申請這塊內存的時候必須把這塊內存加鎖,等申請完了,下一個線程再來申請又要加鎖。這裏如果不對內存進行管理的話,那麼就會導致內存在分配的時候效率是非常低的。


爲了提高內存分配的效率,Oracle就提出一種機制,這種機制叫做TLAB,全稱Thread Local Allocation Buffers。通過名稱我們就可以看到,它對Eden這個區域大致劃分成與線程數量相對應的區域,每個區域分配到一個線程裏去,也就是說線程1只能在TLAB1分配內存,線程2只能在TLAB2內分配內存,依次類推。

那最後還有一塊空間是怎麼用的呢?

當某個線程在當前內存區域分配完了,它就必須在後面這個連續的區域在申請一塊內存出來,也就是說這個線程就關聯了兩個TLAB,它就有很多Buffers。爲了管理這些Buffers,這個線程的Free_list既要指向原來已經佔滿的內存,還要指向新的申請的內存的起始點,後面依次類推可能有N個。這樣就擺脫了鎖的機制,因爲線程都是單線程操作,不存在併發問題,也就是在申請的時候僅僅在初始分配的區域申請即可,不用加鎖,當申請滿的時候在連續的區域申請時纔會加鎖,這就是所謂的併發同步。



4.垃圾回收算法

垃圾回收算法總體上講有3種,第一種叫做複製算法,第二種叫做標記清除算法,第三種叫做標記整理算法,也叫標記清除壓縮算法。


(1)複製算法

wKiom1PyPlGitWgpAADcqyBgVj8778.jpg

複製算法就是把存活的對象從一個空間複製到另一個空間,通過圖可以看到這裏上下分別是一塊空間,他們是完全相同大小一樣的兩塊內存區域。現在假設java虛擬機在運行的某一時刻是輸入上面這種的,根對象(Roots)引用到第一塊空間的A,A又引到了C,那麼現在進行垃圾回收,因爲兩塊內存完全相同大小一樣,那麼僅僅只需要將上面內存中根對象引用到的對象複製出來即可,複製後調整他們之間指針的引用。

但這有個缺點:這樣一來上面這塊內存區域和下面這塊內存區域他們利用率只是50%。Oracle爲了解決這個問題,他會把空間劃分爲3個塊,也就是前面提到的Eden,S0,S1,他複製時並不是把一塊內存等價複製到另一塊內存中,他是把Eden和S0當中存活的對象複製到S1當中,如果S1裝不下,那麼年齡比較大的就會直接晉升到老年代。通過這種方式就大大提高了內存的利用率。這就是Oracle對複製算法的優化,而這種優化確實有效,因爲大多數對象假設的存活時間不是很長,也就是一塊內存回收過後,他的本身佔用量是非常低的,這樣就極大的提高了內存佔用率。


(2)標記清除算法

wKiom1PyP1SiygRMAAEdqyE3PfY505.jpg

標記清除算法和複製算法相比有類似點:他對複製算法的一個最大的好處就是提升了內存的利用率,但有一個缺點:產生了內存碎片。

初始狀態依舊由根引用AA引用C。回收時,通過標記,標記了哪些對象是被引用了,也就是哪些對象不能夠在這次垃圾回收中釋放的對象,標記過後就會執行清除,清除就是把這塊內存遍歷一遍,但這個遍歷也不是一個字節一個字節去遍歷,會有算法會很快地位到哪些對象被標記哪些沒被標記。圖中這裏他會把B對象清除。但清除有一個問題,B釋放的空間以後可能還會用,所以會有一個指針指向這個地方,我們會發現清除過後區域和區域之間不連續,我們把B釋放後的區域叫做內存碎片。


(3)標記清除壓縮算法

wKioL1PyQWHwR5ICAAEjgAG2gcM829.jpg

標記清除壓縮算法的前兩步和標記清除算法是一樣的。但會有一個整理的步驟,意思爲B佔在這個地方,回收的時候會把B後面的C向前挪,回收過後C的位置會發生變化。清除和壓縮的動作就叫做整理。

也就是說標記壓縮算法在標記清除算法之上,是對標記清除算法的一個優化。它不存在內存碎片,但它有個致命的缺點:在整理的過程中會產生一個對象拷貝的代價,這裏就是把C拷貝到B中。而假若這裏有ABCDAC被引用,BD要回收,因此會拷貝B和D,而且這個拷貝還不是一次性拷貝,那麼這個拷貝代價非常高,因此這個算法用的時候要考慮慎重。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章