Java記憶體管理 -JVM 垃圾回收

zzzzMing發表於2018-08-27

版權宣告:本文為博主原創文章,未經博主允許不得轉載

一.概述

相比起C和C++的自己回收記憶體,JAVA要方便得多,因為JVM會為我們自動分配記憶體以及回收記憶體。

在之前的JVM 之記憶體管理 中,我們介紹了JVM記憶體管理的幾個區域,其中程式計數器以及虛擬機器棧是執行緒私有的,隨執行緒而滅,故而它是不用考慮垃圾回收的,因為執行緒結束其記憶體空間即釋放。

而JAVA堆和方法區則不一樣,JAVA堆和方法區時存放的是物件的例項資訊以及物件的其他資訊,這部分是垃圾回收的主要地點。

二.JAVA堆垃圾回收

垃圾回收主要考慮的問題有兩個:一個是效率問題,一個是空間碎片問題。

而Java堆中的垃圾回收可以分為兩個區域,一個是新生代,一個是老年代。其中新生代又分為一塊比較大的Eden空間和兩塊較小的Survivor空間。因為新生代和老年代所儲存的物件群體是不一樣的,為了在效率和空間碎片問題中取得平衡,新生代和老年代所使用的垃圾回收演算法是不一樣。

新生代 -複製演算法

從名字上就知道,新生代主要存放的是比較新的物件,回收多次之後仍然存活的物件,就會被送到老年代中區。由此可知新生代的垃圾回收是比較頻繁的,所以為解決效率問題,新生代使用了複製演算法。複製演算法可以將記憶體分為大小相等的兩塊,每次分配時使用其中一塊,當這一塊用完時,就將還存活的物件複製到另一塊記憶體上面區。此時已使用過的這一塊記憶體就可以一次清理掉,這樣也不用擔心記憶體碎片的問題。當然這種演算法的一個缺點就是記憶體使用率比較低,只有一半(每次只能一半用來分配出去)。

而IBM公司的研究表明,新生代中的物件98%都是”照生夕死“,所以不需要按照1:1劃分,故而會將記憶體分為一塊較大的Eden空間和兩塊小的Survivor空間。

那麼為什麼會有兩塊Survivor呢,複製演算法不是隻需要一塊Eden和一塊Survivor就夠了嗎?

其實這主要還是為了解決碎片化的問題。假設只有一個Survivor區,當Eden區滿的時候,進行Gc,存活物件被分配到了Survivor區,清空Eden區。當再一次Gc完成後,存活的物件繼續放在Survivor區,這樣不是很美好嗎,不會有記憶體碎片啊!但是別忘了,第一次存到Survivor區的物件很可能在第二次Gc的時候就失活了,清理掉Survivor失活物件不就會產生記憶體碎片了嗎?

所以Java堆使用了兩個Survivor區,一個from Survivro和一個toSurvivor,第一次Eden滿的時候,複製演算法將存活物件放到from Survivor區,清空Eden。第二次,Eden滿時,將Eden和from Survivor區存活的物件放到to Survivor區,清空Eden和from Survivor,然後重要的一步,將from Survivor和to Survivor角色互換!這樣就解決了記憶體碎片化的問題。
Java記憶體管理 -JVM 垃圾回收

老年代 -標記/整理演算法

首先要明白老年代存放的都是會存活得比較久的物件,所以如果老年代也使用複製演算法的話,那麼複製物件的開銷時比較大的,因為老年代的物件基本上都會存活。

標記/整理演算法很好理解,主要也就是”標記“,”整理“兩個步驟,先將要回收的物件標記,然後讓存活物件向著一端移動,最後將邊界以外的記憶體,然後Gc完成。
Java記憶體管理 -JVM 垃圾回收

三.方法區垃圾回收

在某些地方的解釋中,方法區也會被叫做“永久代”,與JAVA堆不同,這裡存放的是類的資訊以及一些常量資訊,故而這個區域中被分配的記憶體一般比較難以被回收,所以才有有”永久代“之名。

雖然方法區中垃圾回收效率較低,但被分配的記憶體卻也並非真的就永不被回收,其主要回收的有兩部分內容:廢棄常量和無用的類。廢棄常量的回收與JAVA堆中類例項回收類似,當常量池中一個常量沒有被引用時,就有可能被回收。比如常量池中有一個字串常量“abc”,當沒有任何一個String物件值為"abc"時,那麼下一次垃圾回收"abc"常量就有可能會被回收。

而對於無用的類的回收,首先需要判斷什麼樣的類才是”無用的類“:

  • 該類所有的例項都已被回收,即JAVA堆中沒有該類的例項。
  • 載入類的ClassLoader已經被回收。
  • 該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

虛擬機器可能會堆滿足這三個條件的”無用的類“進行回收,僅僅是可能,並非必然。





如果覺得對你有幫助,不如花0.5元請作者吃顆糖,讓他甜一下吧~~

Java記憶體管理 -JVM 垃圾回收

相關文章