java垃圾回收機制

垃圾回收機制是java的一個重要的特點,也是java面試中經常被問到的一點。首先考慮GC就要考慮3點:一、哪些內存需要回收;二、內存應該在什麼時候回收;三、內存應該被如何回收。
一、哪些內存需要回收
我們知道java中除了8種基本類型(byte(1個字節)、char(2個字節)、int(4個字節)、short(2個字節)、long(8個字節)、double(8個字節)、float(4個字節)、boolean(1個字節)),其他的都是對象類型,而JVM會把對象放到堆內存中,而把系統自動完成分配和釋放的數據放在棧中。顯然堆中存放的對象數據的內存是需要顯式分配的,因而若沒有垃圾回收機制就需要手動的顯式釋放這些內存,而這正是GC需要回收的內存。
二、內存應該在什麼時候回收
當對象已經“死亡”了,即沒有任何引用了,就表示GC可以執行了。那麼如何判斷一個對象是否死亡顯然就是一個問題。常見的有兩種算法。
(1)引用計數算法
引用計數算法就是給對象添加一個引用計數器,每當有一個地方引用該對象,計數器值就加1,當引用消失時,計數器值就減1,任何時刻計數器值爲0的對象就是死亡的對象。但是這個算法有個很重要的缺陷就是它很難解決對象之間循環引用的問題。
(2)可達性分析算法
這是比較主流的算法。它是通過一系列“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索走過的路徑即爲引用鏈,當一個對象到“GC Roots”沒有任何引用鏈可達的話,那麼就說明該對象死亡了。
但是事實上當前通過算法說明該對象死亡了,並不表示它一定會被回收。系統有個finalize()方法,如果該對象覆蓋了該方法的話,那麼該對象至少還要經歷2次標記過程,但是當對象沒有覆蓋該方法或者該方法已經被JVM調用過了,那麼將回收該對象。
所謂的2次標記,第一次即判斷該對象是否要執行finalize(),要執行就標記出來,並將其放到F-Queue的隊列中,然後會執行finalize()方法。而GC會對F-Queue中的對象進行第二次標記,這時候如果對象在執行finalize()方法中重新與引用鏈上任一對象建立聯繫,那麼第二次標記時就將它移出即將回收的集合。finalize()方法也是對象逃脫回收命運的最後機會,而且一個對象的finalize()方法最多隻會被系統調用一次。
三、內存應該被如何回收
垃圾回收有好些不同的算法
(1)標記-清除算法,該算法分爲“標記”和“清除”兩個階段:先標記出需要清除的對象,在標記完成後統一清除所有被標記的對象。該算法有兩點不足:1,算法效率不高,標記和清除效率都不高;2,空間問題,標記清除後會產生大量不連續的內存碎片,導致以後在分配較大對象時會因無法找到足夠的連續內存而提前觸發垃圾回收動作。
(2)複製算法,該算法可以將內存分爲大小相等的兩塊,每次只使用其中一塊,當這一塊用完了,就將還存活的對象複製到另一塊上面,然後再把已使用過的內存空間一次性清除掉,這樣就不用考慮內存碎片了。但是顯然這種算法的代價是讓內存變成原來的一半了。
一般這種算法都是來回收新生代的,而新生代中的對象98%都是很快就死亡的,所以不需要按照1:1的比例來劃分內存空間,而是分爲一個較大的Eden空間和兩個較小的Survivor空間,用的時候用Eden和一個Survivor空間。回收時將還存活的對象複製到剩餘的Survivor上,將使用的Eden和Survivor清空。當Survivor內存不夠,還要依賴老年代內存進行分配擔保。
因爲當前JVM的垃圾回收大都採用“分代回收”算法,所以有新生代和老年代的劃分。這裏面jvm給每個對象定義一個年齡計數器。就是對象沒熬過一次GC年齡就加1,這裏有個年齡閾值,當一個對象的年齡超過這個閾值就劃分到老年代,低於則在新生代。
(3)標記-整理算法,根據老年代的特點,一般採用該算法。標記跟前面的標記一樣,整理是將所有存活的對象都向一端移動,然後回收端邊界以外的內存。

參考:《深入理解java虛擬機》,《java程序員面試寶典》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章