GC可謂是java相較于C++語言,最大的不同點之一。
1.GC回收什么?
上一篇講了內存的分布。
其中程序計數器棧,虛擬機棧,本地方法棧 3個區域隨著線程而生,隨著線程而死。這些棧的內存,可以理解為在編譯期已經確定。
方法結束,或者線程結束時,內存就自然被回收了。
一個interface的多個實現類,需要的內存可能不一樣,一個方法的多個分支需要的內存也不一樣,我們只有在程序運行的時候,才知道會創建那些對象,需要多少內存。
這部分分配和回收都是動態的,GC所關注的就是這部分內存。
2.回收的標準:
java堆里面幾乎存放著所有的對象實例。對象實例如果已經不再被使用,那這段內存就應該被回收,這個時候就是GC上場的時候。
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標記清楚算法:
先標記需要回收的內存,標記完成后,統一回收。
這個是最基礎的算法,其他算法都是以此為基礎。
3.2 復制算法
就是將內存分為大小相等的2塊,每次只使用一塊,當一塊用完的時候,就將還存活的部分,復制到另一塊空間,然后
把已經使用的那塊做一次性清理。
這樣就不用考慮內存碎片的情況。只是這種算法代價就是一半的內存空間。
3.3 標記--整理算法
讓所有存活的對象移向一端,然后直接清理掉端邊界以外的內存。
3.4 分代算法
根據對象存活周期的不同,分為新生代,和老年代。
在新生代,每次回收都有很多對象被回收,可以選用復制算法,只需要少量存活的對象復制成本就可以。
而老年代,存活的時間比較久,必須使用標記清楚或者標記整理方法。
3.4.1 枚舉根節點
在選擇處理GC Roots方法時,GC Root所在的上下文可能有數百兆,所以在判斷GC Roots的時候,應該確保一致性,JVM
應該停止所有java執行線程。所以GC是占用CPU作為代價!
4.內存分配策略
4.1對象優先在Eden分配
大多數情況下,對象在新生代的Eden區中分配內存,當Eden區內存不足時,java虛擬機將發生GC,
釋放不需要的對象。
4.2 大對象直接在老年代
大對象,是指需要大量連續空間的對象,比如很長的字串或者數組。
比大對象更糟糕的事情就是,遇到一群很快無用的 大對象。
4.3 長期存活的對象將進入老年代
對象開始放在新生代eden區,如果度過一次GC,進入Survivor空間,對象年齡設置為1.
以后,每一次GC,年齡就加1。
當年齡到達一定的閥值以后,就回進入老年代。
新聞熱點
疑難解答