jvm 內存分配與垃圾回收

一般粗略的可以把內存的分配分爲棧區和堆區,但是對於理解垃圾回收和分配還要分得細一點。如下圖:
這裏寫圖片描述
分爲堆和方法區,虛擬機棧和本地方法棧,程序計數器。
前兩個是多線程共享的,後面三個是每個線程單獨的。

方法區

保存的是加載的類信息,常量(有常量池),靜態變量等與實例無關與類有關的信息。裏面還包括運行常量池,會在類加載時,把class文件常量池的字面量,符號引用等裝進運行常量池。使用String的intern()方法時也會把常量裝入運行常量池。

就是存放對象實例的地方,也是垃圾回收器管理的主要區域。它的詳細劃分將在後面說。

java虛擬機棧

當進入一個方法的時候就會創建一個棧幀,裏面存放局部變量表,操作數棧,動態鏈接(不知道是什麼),方法出口信息。每個方法的調用就是棧幀在虛擬機棧中出棧和入棧的過程。

本地方法棧

和虛擬機棧類似,不同的是它存放的是調用native()方法的存儲的數據。

程序計數器

就是記錄代碼執行的地址的,代碼執行的行號指示器。

對象創建

當虛擬機遇到new指令時,檢查指令後面的參數能否定位到常量池中的一個類的符號引用,並檢查符號引用的類是否已經加載,解析,初始化過。

內存分配的方式

如果內存是連續的,則計算創建的對象的大小,並移動指針同樣大小的距離進行分配,這種方式叫“指針碰撞”。
如果內存不連續,用”空閒列表“記錄沒有使用的空間。

解決併發分配內存問題

一種是使用CSA和失敗重試的方式保證指針更新的原子性(不知道CSA是什麼只知道可以處理併發)
第二種是,每個線程劃分自己的TLAB(本地線程分配緩衝),每個線程的指針在自己的緩衝區上移動。

句柄和直接指針

句柄就是指向指針的指針,直接指針就直接指向的對象實例的堆地址
這裏寫圖片描述

判斷對象的死亡

引用計數法

給對象添加一個引用計數器,當有一個地方引用它的時候,+1。引用失效時,-1。
問題,當兩個對象相互引用時,對象已經無法訪問了,但是因爲計數器不爲0,GC無法回收他們。

ObjA obja = new ObjA();
ObjB objb = new ObjB();

obja.instance = objb;
objb.instance = obja;

似乎表難理解什麼時候有引用,什麼時候失效.

可達性分析算法

以GC Roots對象爲起點,搜索它的引用鏈。如果存在引用該對象則可達。沒有則不可達。
可作爲GC Roots的對象

  • 虛擬機棧中的局部變量表中的變量
  • 本地方法棧中引用的變量
  • 方法區常量引用的對象
  • 方法區靜態變量引用的對象

垃圾收集算法

分代收集算法
只是定義了新生代和老年代使用不同的收集算法
新生代因爲對象生存週期短使用複製算法,老年代則使用標記-清除和標記整理算法

標記-清除

每個對象不可達後會被標記,並篩選。
篩選的標準是,如果對象沒有執行過finalize方法或覆蓋了finalize方法,則把它放到一個隊列,會有一個優先級比較低的線程執行對象的finalize方法。在執行finalize方法時,可以使對象重新被引用到。這樣就不會被回收。這是對象拯救自己的最後的機會。但是可能因爲執行finalize線程的優先級比較低,所以作者建議不要依賴這個方法。
如果標記後沒有被篩選到隊列,那麼真的就會被回收。另外finalise方法只能執行一次。

複製算法

把堆分爲eden space,form survival,to survival。對象分配在eden,每次gc後,將eden和survival存活的對象放到另一個survival。當存活的對象大於survival時,就需要依賴老年代(老年代使用其他的算法回收對象)。

標記-整理

讓存活的對象向一段移動,直接清除邊界外的內存。

內存分配

對象優先進入eden

大對象直接進入老年代

經歷過一定次數的對象會進入老生代

動態對象年齡判定

空間分配擔保

如果老年代的空間大於,新生代的總對象的大小,則MinorGC是安全的。否則根據HandlePromissionFailure設置是否冒險。冒險是指,根據上一次虛擬機會檢查歷次放入晉升到老年代的對象的平均大小,如果小於則執行MinorGC否則FullGC。

發佈了31 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章