深刻理解java虛擬機(2)------垃圾收集器和內存分配策略

GC可謂是java相較於C++語言,最大的不一樣點之一。java

1.GC回收什麼?

上一篇講了內存的分佈。算法

其中程序計數器棧,虛擬機棧,本地方法棧 3個區域隨着線程而生,隨着線程而死。這些棧的內存,能夠理解爲在編譯期已經肯定。數組

方法結束,或者線程結束時,內存就天然被回收了。this

 一個interface的多個實現類,須要的內存可能不同,一個方法的多個分支須要的內存也不同,咱們只有在程序運行的時候,才知道會建立那些對象,須要多少內存。線程

這部分分配和回收都是動態的,GC所關注的就是這部份內存。指針

2.回收的標準:

java堆裏面幾乎存放着全部的對象實例。對象實例若是已經再也不被使用,那這段內存就應該被回收,這個時候就是GC上場的時候。對象

虛擬機棧,程序計數器,本地方法棧,這些都會隨着線程的消亡而消亡。因此這些不須要考慮內存的回收。ublog

GC的回收特指Java堆 & 方法區的內存。內存

2.1 引用計數法:

給對象一個引用計數,當計數爲0的時候,能夠理解爲,該對象再也不被使用,能夠釋放內存。開發

可是這個方法很難解決一個問題:對象間的相互引用問題。

舉個例子:

對象objA和objB都有字段instance。

objA.instance = objB;

objB.instance = objA;

它們沒有其餘引用。

可是它們的引用計數不可能爲0.因而沒法通知GC回收它們。

2.2 可達性分析算法:

主流的商用程序語言的主流實現中,都稱爲可達性分析。

這個算法的基本思想經過GC Roots的對象做爲起始點。當一個對象,到起始點,沒有鏈接的時候,能夠理解爲,這個節點的內存能夠釋放。

從圖中能夠看到,object5,object6,object7 這些對象會被GC回收。

 

2.3 引用

不管經過何種算法來實現GC,都和引用有關。

java1.2以後,引用分爲 強引用,軟應用,弱引用,已級虛引用。

強引用是java廣泛存在的一種狀態,相似new 一個對象。強引用不會被GC回收。

軟引用,軟引用是描述還有用,但未必要存在的對象。它的生存時間是,當內存不足時,也就是快要OOM的時候,GC會回收它。

弱引用,就是非必要的的對象。被弱引用指向的對象,只能生存到下一次GC以前。

虛引用,虛引用的目的是爲了當GC發生時,能夠收到一個系統通知。不會對引用對象產生任何影響。

2.4 對象如何判斷要回收:

GC會對不可達的對象,作第一次標記。

當該對象執行finalize方法後,或者沒有覆蓋finalize方法,這回被第二次標記,這個時候,GC會被這段內存清理。

當對象執行finalize方法時,JVM會把它放在一個F-queue裏面,這是這個對象實例拯救本身的最後機會。

它只須要在finalize裏面,把this賦給某個變量。

任何一個對象finalize只會被執行一次。

 

3.垃圾收集的算法

3.1標記清楚算法:

顧名思義,分2端過程,先標記,再清除。先標記須要回收的內存,標記完成後,統一回收。

這個是最基礎的算法,其餘算法都是以此爲基礎。

2個缺點:效率問題,標記和清除效率都不高。內存碎片,標記清除以後會產生大量的空間碎片。若是申請大的內存

可能會申請不到,而不得不提早觸發下一次GC

3.2 複製算法

就是將內存分爲大小相等的2塊,每次只使用一塊,當一塊用完的時候,就將還存活的部分,複製到另外一塊空間,而後

把已經使用的那塊作一次性清理,這種清理速度很是快。

這樣就不用考慮內存碎片的狀況。只是這種算法代價就是一半的內存空間。

3.3 標記--整理算法

標記過程同前面同樣,標記結束後,把內存塊移到一塊兒。(這裏的移動是指針移動,邏輯內存移動,物理內存應該沒有變化。)

讓全部存活的對象移向一端,而後直接清理掉端邊界之外的內存,清理速度很快,可是內存移動是耗時?。

從實現上說,清理應該是和硬盤刪除文件同樣,不是真正的刪除,而是把對應的內存標記爲 未使用。而且把文件名刪除。

3.4 分代算法

根據對象存活週期的不一樣,分爲新生代,和老年代。

在新生代,每次回收都有不少對象被回收,能夠選用複製算法,只須要少許存活的對象複製成本就能夠。複製成本地,而且發生機率高,清理速度快。

而老年代,存活的時間比較久,發生機率地,對空間要求高,對時間要求低。必須使用標記清楚或者標記整理方法。

如今商業虛擬機的配置,因爲98%的對象可能會很快被釋放,因此複製算法並不是是1:1最合理。

HotSpot(Sun公司開發的虛擬機)分配就是一塊較大的Eden空間& 2塊Survivor空間。

比例位Eden:Survivor = 8:1.複製算法發生時,Eden+1Survivor 剩餘的內存 copy到1Survivor上。

3.4.1 枚舉根節點

在選擇處理GC Roots方法時,GC Root所在的上下文可能有數百兆,因此在判斷GC Roots的時候,應該確保一致性,JVM

應該中止全部java執行線程。因此GC是佔用CPU做爲代價!

4.內存分配策略和回收

java所解決的內存的管理歸根到底就是2個問題,內存的分配 & 已分配內存的回收。關於回收,咱們已經很詳細的分析GC的算法。

4.1對象優先在Eden分配

大多數狀況下,對象在新生代的Eden區中分配內存,當Eden區內存不足時,java虛擬機將發生GC,

釋放不須要的對象。

4.2 大對象直接在老年代

大對象,是指須要大量連續空間的對象,好比很長的字串或者數組。

比大對象更糟糕的事情就是,遇到一羣很快無用的 大對象。

大對象佔據內存空間,尤爲這些對象使用頻率不高,這樣就浪費了不少空間。容易照成GC的頻率過多。

4.3 長期存活的對象將進入老年代

對象開始放在新生代eden區,若是度過一次GC,進入Survivor空間,對象年齡設置爲1.

之後,每一次GC,年齡就加1。

當年齡到達必定的閥值之後(默認是15),就回進入老年代。

4.4 空間分配擔保

爲了充分利用新生代的內存空間,複製算法並無1:1的來分配。因此有機率當Eden還保留大量的對象時,1個Survivor沒法複製所有的對象

這個時候就須要部份內存放入老年代。But 若是老年代內存不足呢?那隻能繼續GC,以確保有足夠的空間。

相關文章
相關標籤/搜索