Java虛擬機讀書筆記

Java虛擬機運行時數據區域

Java虛擬機運行時數據區域就是Java虛擬機管理的內存區域。這個數據區域分爲方法區、堆、虛擬機棧、本地方法棧、程序計數器


程序計數器(線程私有)

對於Java程序計數器來說,如果執行Java方法,那麼計數器記錄的是正在執行的虛擬機字節碼指令的地址,如果執行Native方法,那麼計數器的值爲空(Undefined)


虛擬機棧(線程私有)  

每個Java方法在執行的時候都會創建一個Stack Frame,這個Stack Frame用來存儲局部變量表、操作數棧、動態鏈接、方法出口等。每個方法執行過程都是一個Stack Frame在虛擬機棧入棧到出棧的過程。其中局部變量表保存了各種基本類型和對象引用以及方法的返回地址(即字節碼指令地址)。這個局部變量表在編譯期間就完全確定了,在方法運行期間是不會改變的。如果當線程請求的棧深度大於虛擬機棧允許的深度,那麼拋出StackOverflowError,當虛擬機棧動態拓展時無法申請到足夠的空間,那麼拋出OutOfMemoryError。


本地方法棧

與虛擬機棧類似 堆是Java虛擬機所管理的內存中最大的一塊,被所有線程共享,它是在虛擬機啓動的時候創建的。堆就是用來創建對象實例和數組的。堆一邊被分爲新生代和老年代,其中新生代還可以被分爲Eden空間、From Survivor空間和To Survivor空間。


方法區

又名永久帶,這只是Hotspot的一個處理,他們用永久帶來實現方法區。用來存儲虛擬機加載的類、常量、靜態變量、即時編譯器編譯後的代碼。其中方法去裏面還包括了運行時常量池,因爲一個Class文件裏除了類的版本、字段、方法、接口等描述信息之外還有很多編譯期間產生的各種字面量和符號引用,而這些都會在運行時常量池裏面保存。當然,在運行期間產生的新的常量池也會被放到池裏面去。String的intern方法就是利用這個特性。


對象的創建過程

1、碰到new的時候,首先檢查new這個指令裏面的參數是否能在常量池裏面定位到,並且堅持這個符號引用代表的類是否被加載、解析和初始化過。如果沒有,需要先執行相應地類加載過程

2、類加載檢查通過之後,開始爲新生對象分配內存。對象所需的內存大小在類加載完之後即可確定了。分配對象的過程就是將一塊確定大小的內存從Java堆裏面劃分出來。加入Java堆中的內存是規整的,即所有內存都是放到一邊的,空閒的在另一邊,中間有指針作爲分界點。那麼這種分配內存的過程就是指針移動相應內存大小的距離,這種內存分配方式就是“指針碰撞”;而另外一種分配方式即在內存不規整的前提下進行的,這是虛擬機需要維持一個空閒內存記錄表,每次要分配新內存的時候在表裏面進行查找。

3、考慮每次分配內存的時候存在多個線程競爭,那麼就需要使用TLAB這種解決方案

4、分配完之後,需要進行空間初始化,這樣可以保證對象的實例字段可以不賦值即可使用

5、對象進行相應地設置,比如對象屬於哪個類,如何找到類元數據信息、對象的hashcode、對象的GC分代年齡。這些都保存在對象頭裏面。

6、最後執行<init>方法


對象的內存佈局

三個部分:對象頭(Header)、示例數據(Instance Data)、對齊填充(Padding)


內存的訪問定位

兩種方式:句柄和直接。其中句柄方式就是在堆裏面維持一個句柄池,然後當通過棧裏面的地址先找到句柄,句柄包含了存在堆裏的對象實例數據和存在方法區的對象類型數據的地址。這樣做的好處比較靈活,當因爲垃圾回收等原因造成對象實例移動,只需改變句柄即可;另一種方式,直接訪問,即棧裏面的地址直接訪問堆裏面的對象實例,其中對象實例裏面還包括的了方法區裏面的對象類型數據的地址。


垃圾回收以及內存分配

三個目的:哪些內存需要回收、什麼時候回收、如何回收哪些內存需要回收也就是如何判斷需要回收的內存。

a、引用計數法

就是通過給對象添加引用計數,當有引用它的時候,計數加1,引用失效時,減一,但是由於存在相互引用,沒被採用。

b、可達性

通過從一個GC Roots的起點開始搜索可達對象,不可達對象即可被判斷爲可回收對象。其中GC Roots包括這些:虛擬機棧中引用的對象、方法區中類靜態屬性引用的對象、方法區中常量引用的對象、本地方法棧中JNI引用的對象


引用的分類

強引用(默認就是)、軟引用(在發生內存溢出之前,這些對象會被列入第二次回收的範圍,如果這次還沒回收足夠的內存,拋出異常)、弱引用(當垃圾回收器進行工作的時候,無論當前內存釋放足夠,被弱引用關聯的對象都會被回收)、虛引用(在對象被回收的時候收到系統通知)



對象回收的再判定

在經歷過可達性分析之後呢,其實只是給這個要回收的對象判了一個緩刑。當這個對象沒有覆蓋finalize()或者虛擬機已經調用過它的finalize方法,那麼它就被視爲沒必要執行。如果對象被視爲有必要執行垃圾回收,它會被放到F-Queue隊列裏面,那麼它接下來會被一個FInalizer的線程去執行。GC這時會對這個隊列進行第二次標記,如果這個時候對象沒有在finalize方法裏重新引用自己的話,那麼就會被真的回收了。


方法區的垃圾回收(也就是永久帶的垃圾回收)

這裏面主要是對廢棄的常量和無用類的回收。無用類的判斷就是三個條件:該類所有實例被回收、加載該類的ClassLoader被回收、該類對應的Class對象沒有任何地方被引用,無法通過任何地方被反射訪問該類的方法。


什麼時候回收

在內存空間不足的時候,不能夠分配新的對象的時候。如果發生在新生代,那麼就是新生代GC,即MinorGC,發生在老年代,那就是Major GC。


如何回收(即垃圾回收算法)

a、標記-清除

兩個階段。首先標記所有要回收的對象,然後統一回收。

b、標記-複製(新生代)

把內存分爲兩塊大小相等的,然後當一塊用完的時候,將存活的對象複製到另一塊上面去,然後把已經使用過的清理掉。後來這樣由於導致內存使用率不高,因爲IBM發現新生代的對象98%的都是很快就死掉了,因此後來就演變成將內存分爲較大的Eden空間和兩塊較小的Survivor空間,Eden和Survivor的比例爲8:1。每次使用Eden和其中一塊Survivor空間,當內存不夠用時,會將Eden和Survivor中還存活的對象(大部分都不到10%)複製到另一塊Survivor空間,然後再將之前那的Eden和Survivor空間清除。當然還有一些意外的情況,就是萬一之前存活的對象超過了10%,那麼就會使用分配擔保機制進入老年代。

c、標記-整理(老年代)

類似於標記-清理,只不過後來是通過將移動所有存活的對象,然後清理相應地端邊界以外的內存。

d、分代收集



內存分配

對象優先在Eden分配大對象直接進入老年代長期存活對象進入老年代(每經歷過一次Minor GC,對象年齡加一。默認年齡的閾值是15,即這之後就要進入老年代了。)

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