堆區是用來存儲new出來的對象的,當對象填充滿堆區後,就會導致內存爆掉,程序就GG了。
就需要科學的進行GC:
首先需要判斷這個對象是否應該被刪除,如果應該被刪除,那麼需要將這個對象清理掉。
判斷的標準:GCRoot(一般是指被棧上的直接或間接引用、本地方法棧直接或間接引用的對象、方法區的j靜態static變量或常量直接或間接引用的對象)
和GCRoot沒有相連的關係的就可以刪除。
清理堆區對象的思路:
- 標記-清理(對象後打標,如果應該被刪除就標記一下,然後再掃描一次,把含有標記的對象刪除掉)
但是標記清理算法的缺點是產生內存碎片。【就像衣服打洞】
- 標記-整理(清除過後,後面的對象補上來)
這樣就減少了內存的碎片,但是代價太大,所有對象都需要前移。
- 複製算法:
複製算法將內存1分爲2.在1區上創建的對象標記是否需要刪除,等到快滿的時候,不是直接將這個對象刪除,而是往2區進行復制,
需要刪除的就不復制過來了,不需要刪除的這些就緊湊的複製過來。這樣就避免了內存碎片問題和開銷也不大。
但是其缺點就是需要2倍的內存。
實際GC過程:
將堆區進行劃分爲:年輕代、老年代。
對於年輕代又進行劃分爲3個區:E區、S(surive)0區、S1區。
而老年代就只有1個區。
整個過程在進行new對象的時候它其實是產生在E區的。當E區快滿了的時候,就會觸發Young區的GC,採用的是複製算法。會對需要刪除的對象上打標記,不需要刪除的對象依次複製到S0區。
這裏有2點疑問:
- E區比S區大
- 有2個S區
E區比S區大的原因是對象都有個特點:“朝生夕死”,很容易就夭折了,倖存下來的比較少。比例大概是1:1:8
需要2塊S區是因爲需要交替工作的,即E區打完標記倖存後放入S0區,然後需要將E區和S1區刪除,然後等下一次E區快滿的時候將S0區、E區所有對象進行打標全部複製到S1區。S0和S1交替使用作爲倖存下來的區域。
即E+S1複製到S0、E+S0複製到S1、E+S1複製到S0。。。。。反覆執行。
比複製算法直接1分爲2內存的利用率高點。
然後再看old區:
其實每次Young GC這個對象的年齡就會+1,即如果在這次GC後這個對象活下來了其年齡就+1.
如果age=15,就不再往S區複製了而是直接到Old區。
Old區除了存了年齡》=15歲的對象還存儲“大對象”(大對象的複製消耗比較大,所以直接存到old區)
如果Old區快滿的時候也會進行GC,Old GC一般會伴隨Young GC,所以Old GC又叫Full GC,會引起整個java程序暫停然後全力的進行垃圾回收,通過標記清理或標記整理的算法。
總結:標記清理或標記整理主要用於Full GC,複製算法主要用於Young GC.
年輕代中比較有名的收集器是ParNew,老年代中比較有名的垃圾收集器是CMS。
最新版的JDK不再建議用以前的垃圾收集器了,而是採用全新的G1垃圾收集器