這篇文章主要從如下幾個方面介紹垃圾收集的相關知識java
1、判斷對象是否已死面試
2、主流垃圾收集算法算法
3、內存分配與回收策略數組
本章節主要從如下幾個思考點着手介紹垃圾回收的相關知識:哪些內存須要回收?何時回收?如何回收?這也是經典的學習一個知識點的3h方法:what? when? how?jvm
上一個章節已經介紹jvm運行時數據區的內存分佈,垃圾回收主要發生在堆這個區,也就是衆多對象實例呆着的地方學習
1、如何判斷對象已死?設計
相信面試太高級java的工程師確定遇到過面試官這樣的問題:兩個對象之間互相循環引用,如何回收?換句話說如何判斷這兩個對象已死?對象
這裏就引出了一個經典算法:引用計數法。是這樣設計的:給對象添加一個引用計數器,每當這個對象被引用時,計數器值+1,引用失效時,計數值-1,任什麼時候刻,計數爲0blog
的對象就是不可能再被使用的對象內存
回到上面的例子,它很難解決互相引用的對象這種狀況,因而致使GC沒法回收他們
因而出現了另外一個經典算法:可達性分析算法,基本思路是經過一系列的稱爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,
當一個對象到GC Roots沒有任何引用鏈相連,或者稱從GC Roots到這個對象不可達時,則證實這個對象是不可用的
做爲GC Roots的對象通常有如下幾種:虛擬機棧中引用的對象,方法區中類靜態屬性引用的對象,方法區中常量引用的對象,本地方法棧中JNI引用的對象(這一種不太熟悉)
關於java對引用的概念細分爲強引用,軟引用,弱引用,虛引用的細節,能夠閱讀該書瞭解,在此再也不介紹
2、幾種常見的垃圾收集算法
一、Mark-Sweep
標記-清除算法,分爲標記,清除兩個階段,首先標記出全部須要回收的對象,標記完成後統一回收
不足:效率不高;回收後產生大量不連續的內存碎片。碎片太多的話,須要分配大對象時,沒法找到足夠的連續內存而不得不提早觸發新一輪的垃圾收集動做
二、Copying
複製算法,大多商業虛擬機都採用這種收集算法回收新生代,具體的在第三點內存分配會講到
三、Mark-Compact
標記-整理算法。通常使用於老年代
四、Generational Collection
分代收集算法。當前商業虛擬機的垃圾收集都採用分代收集算法,通常是把Java堆分爲新生代和老年代,根據各自特色採用最適當的收集算法。
3、內存分配與回收策略
Java的自動內存管理機制能夠歸結爲解決了兩個問題:給對象分配內存以及回收分配給對象的內存。Java的內存分配策略並非絕對的或者固定的,這取決於
當前使用的垃圾收集器組合,以及虛擬機中與內存分配相關的參數設置,接下來說的是最爲廣泛的內存分配規則
(1)對象優先在Eden區分配
新生代區域通常被分爲較大的Eden空間和兩塊較小的Survivor空間(一般稱爲From和To),HotSpot虛擬機默認Edon和兩個Survivor的大小比例是8:1:1,
新建立的對象通常會在Edon和From中,當Edon區沒有足夠的空間進行分配時,將觸發一次Minor GC,前面講過大多數對象是朝生夕死的,所以Minor GC很是頻繁
當一次Minor GC事後,仍然存活的對象會一次性複製到To區域中,而後清理掉Edon和From;這時候注意,From和To將交換角色,如今新的To是清理後的From
所以To區域總能保證每次Minor GC後留有必定的空間容納尚存活的對象
(2)長期存活的對象將進入老年代
虛擬機給每一個對象都定義了一個對象年齡(Age)計數器,在Edon出生的對象通過第一次Minor GC後仍然存活,並能在Survivor容納的話Age將設爲1,在Survivor區
每熬過一次Minor GC,Age+1,當Age達到設置的參數值-XX:MaxTenuringThreshold(默認值15),將晉升老年代,關於晉升老年代的條件並不是必定要達到這種狀況,
java虛擬機有動態對象年齡斷定策略,具體可閱讀本書3.6.4細節
(3)大對象直接進入老年代
所謂大對象指的是須要大量連續內存空間的對象,最典型的如很長的字符串以及數組,常常出現大對象意味着極可能內存中還有很多空間時就得提早觸發垃圾收集
以獲取足夠的連續空間來安置他們。
(4)觸發Full GC的條件
Full GC的速度通常比Minor GC慢10倍以上,觸發一次Full GC常常會伴隨一次Minor GC,一種觸發條件爲,一次Minor GC發生後將要晉升爲老年代的對象大小超過
老年代現有剩餘空間大小,這種情形不難想象。java的老年代空間分配擔保細節可細讀3.6.5節知曉,此處再也不細說
最後上圖一張做爲結尾,一目瞭然: