當前位置:才華齋>計算機>java語言>

Java記憶體回收

java語言 閱讀(2.47W)

Java的GC機制是自動進行的,和C語言有些區別需要程式設計師自己保證記憶體的使用和回收。下面是小編分享的具體介紹,一起來看一下吧。

Java記憶體回收

Java的記憶體分配和回收也主要在Java的堆上進行的,Java的堆中儲存了大量的物件例項,所以Java的堆也叫GC堆。

下面主要說一下對於java堆的記憶體回收 。

 什麼樣的記憶體可以回收

判斷法1:引用計數

方法:每有一個引用指向這個物件,那麼這個物件的引用計數+1,反之,每有一個引用改變了指向,那麼他原來指向的物件引用計數-1,當引用計數為0的時候,這個物件也就不可能被使用了那麼就可以被回收了

問題:可能會出現環狀的引用,導致不可能被使用的物件永遠不可能被回收

示例:

Class A {

A a;

Public static void main(String[] args){

A gc1 = new A();

A gc2 = new A();

Gc1.a = gc2;

Gc2.a = gc1;

Gc1= null;

Gc2 = null;

}

Gc1和gc2都被設定成null了,他們都應該被清理,但是因為gc1的a物件指向gc2,gc2的a物件指向gc1,導致他們的引用計數永遠為1,但是他們都永遠不可能被使用了,所以這種方法存在漏洞

判斷2:可達性分析演算法

方法:從一個叫做GC ROOTS的節點出發,所有能夠到達的引用物件標記起來,直到走到完全沒有引用的地方為止,這樣從這個節點連起來的所有的點(引用鏈),構成的路線就是不可回收的,那麼所有沒有被到達過的物件均可以被回收

什麼可以做GC ROOTS:虛擬機器棧(棧幀中的本地變量表)中的引用的物件、方法區中類靜態屬性引用的物件、方法區中常量引用的物件、本地方法棧中JNI引用的物件

這些物件的特點:不可變並且隨時可能被用到,生命週期長

補充:可達性分析的演算法,那麼沒有在引用鏈上的物件都一定會被清理嗎?不一定。當執行可達性分析的演算法之後,會對所有沒有在引用鏈上的物件進行一次標記和篩選,篩選的條件為:該物件覆蓋了finallize()方法(這個方法是GC的時候如果這個物件要被回收則執行的方法,但是在Thinking in java中不推薦用來處理收尾工作),並且這個方法沒有被執行過,那麼就會把這個物件放到一個低優先順序佇列中執行,也就是這個物件的最後搶救的機會,如果這個時候這個物件把自己和在引用鏈上的引用連了起來,那麼他在執行完finallize方法之後,再次判斷時就不會被清理,否則會在第二次可達性判斷的時候直接清理(因為finallize已經執行過一次了),如果沒有覆蓋這個方法,那麼對不起,再見

  回收演算法介紹:

回收演算法1:標記清理(Mark——sweep)演算法

標記所有需要回收物件,然後將他們清理回收

問題:會產生記憶體碎片

優點:不需要暫停所有執行緒(Stop the world)

回收演算法2:複製

標記後,將所有不需要回收的物件全部複製到一個空的記憶體中,然後清理剛剛使用的記憶體塊

問題:浪費資源,會有一些記憶體堆無法被使用

解決:用在新生代,新生代會有80%以上的物件經過一次GC就會死亡,因此採用Eden + Survivor * 2的辦法,Eden = 8 * Survivor大小(HotSpot預設),那麼每次使用一個Eden + 一個Survivor,然後進行復制清理的時候,清理Eden + Survivor中,然後將可用的物件複製到空閒的Survivor中,然後全部清空前面的使用區,然後使用Eden 和複製到的Survivor

又一個問題:如果Survivor不夠怎麼辦?向老年代借空間,叫做分配擔保,不夠存放的`物件會通過分配擔保進入老年代

問題:需要 stop the world

回收演算法3:標記整理(Mark——compact)

標記後,將可用記憶體向一側擺放,然後清理掉可用記憶體邊緣外部的所有記憶體區域

優點:沒有記憶體碎片的問題

問題:需要stop the world

回收演算法4:分代收集

將堆分代(老年代、新生代),老年代採用標記整理、標記清理等方法,新生代採用複製方法。

為什麼:因為老年代大部分物件是可用的,因此如果採用複製演算法,雖然沒有記憶體碎片,但是空間浪費大,而且大部分物件沒有變化,而在新生代使用複製演算法,可以犧牲很小的記憶體空間就獲得較好的效率

  HotSpot中記憶體回收演算法

列舉根節點(GC ROOTs)

Java虛擬機器採用準確式GC,有一個OOPmap來標記哪個位置有個物件,這樣在查詢引用鏈的時候可以較快的找齊所有的引用鏈

Safepoint

在OOPmap的協助下,這個可以快速且準確的完成列舉,但是問題就是導致這個oopmap變化的指令非常多,如果為每一條指令生成oopmap,那麼會需要大量額外空間,因此採用在特定點的地方生成,這些點同時也是safepoint的點,那麼當需要列舉根節點的時候,就讓執行緒執行到這個地方再停止。

一種方法:先停止所有執行緒,然後再讓沒有到安全點的執行緒自己跑到安全點再停下來,基本不適用,搶先試中斷

另一種方法:主動式中斷,設定一個標記,在執行的時候去輪詢,而需要中斷的時候,直接將這個位置的記憶體設定不可達,那麼執行緒就會進入一個自陷異常,就自己會中斷