哪些內存需要回收?
由於程序計數器、虛擬機棧、本地方法棧是線程私有的,即隨着線程生而生,線程死而死;而java堆和方法區是線程共享的,所以垃圾回收GC面向java堆(主要)和方法區。
垃圾是什麼?
一個對象,使用對象存活判定算法判定,發現不是存活狀態,即爲可回收的垃圾。
對象存活判定算法:
1. 引用計數算法:
- 給對象添加一個引用計數器,每當有一個地方引用它時,計數器加1;當一個引用失效時,計數器減1。對一個對象,其引用計數爲0,則未被引用,即判定爲可回收的垃圾。
- 優點:算法簡單,效率高
- 缺點:不能解決對象之間相互循環引用的問題
- eg:兩個對象,除了互相引用之外,再無任何其他的引用,實際上這兩個對象已經不可能被訪問了,但是通過引用計數算法不能判斷爲可回收的垃圾。
2. 可達性分析算法(目前一般都用這種):
- 從一個“GC Roots”出發,向下搜索,到某一個對象節點沒有任何路徑可達,則稱“GC Roots”到這個對象不可達,即該對象不可用,可以判定爲可回收的垃圾。
- 可作爲 GC Roots的對象包括:
- 虛擬機棧中引用的對象
- 方法區中靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中Native方法引用的對象
- 可作爲 GC Roots的對象包括:
- 優點:解決了引用計數算法不能解決的循環引用的問題
引用(插入講解)
引用分爲4種類型,從強到弱依次是:
強引用(Strong Reference) > 軟引用(Soft Reference) > 弱引用(Weak Reference)> 虛引用(Phantom Reference)
- 強引用:代碼中普遍存在的引用形式,只要強引用存在,就不能GC回收
- 軟引用:描述一些還有用但非必需的對象,當內存不足,即將發生OOM(Out Of Memory)異常時,會加入GC回收範圍,下次進行回收
- 弱引用:同樣是描述非必需的對象,但比軟引用強度弱,即不管內存是否充足,下次進行GC時,會被回收
- 虛引用(幽林引用/幻影引用):能在這個對象被收集器回收時受到一個系統通知
垃圾回收的方法?
垃圾收集算法,大致分爲以下4種:標記-清除算法、複製算法、標記-整理算法、分代收集算法,其中標記-清除算法是最基礎的收集算法,後面的算法都是在其基礎上不斷進行改進而產生的。
1. 標記-清除算法(Mark-Sweep):
- 分爲兩個過程:標記和清除,先標記出所有需要回收的對象,標記完成後,再統一回收所有被標記的對象。
- 缺點:
- 標記和清除的效率都不高;
- 會形成大量不連續的內存碎片,導致後面對較大對象的分配出現內存不足,而提前觸發另一次GC。
2. 複製算法(Copying):
- 將可用內存一分爲二,每次只使用其中的一塊;當一塊用完了,就將還存活的對象複製到另一塊上面,再將前一塊中使用過的內存空間一次性清理掉。
- 解決了效率和內存碎片的問題
- 缺點:將內存縮小爲原來的一半
- 一般用於新生代
3.標記-整理算法(Mark-Compact):
- 分爲兩個步驟,標記和整理,標記和標記-清除算法中類似;至於整理是指:將所有存活對象都向一端移動,然後直接清理掉端邊界以外的內存。
- 一般用於老年代
4.分代收集算法:
- 將Java堆分爲新生代和老年代
- 新生代:對象大量死亡,少量存活,選用複製算法
- 新生代中將內存分爲3塊:一塊較大的Eden、兩塊較小的Survivor,每次使用Eden和一塊Survivor
- 當回收時,將Eden和Survivor中的存活對象複製到另一塊Survivor中,再清理掉Eden和第一塊Survivor空間
- HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,即每次新生代可用內存空間爲整個新生代容量的90%
- 老年代:對象存活率高,選用標記-清理算法或標記-整理算法
- 新生代:對象大量死亡,少量存活,選用複製算法
內存分配和回收策略
- 對象優先在Eden分配
- 大對象直接進入老年代
- 長期存活的對象將進入老年代
- 動態對象進行年齡判定
- 如果Survivor中相同年齡所有對象大小的總和大於Survivor空間的一半,則年齡大於或等於該年齡的對象就可以直接進入老年代