任何語言在運行過程中都會創建對象,也就意味着需要在內存中爲這些對象在內存中分配空間,在這些對象失去使用的意義的時候,需要釋放掉這些內容,保證內存能夠提供給新的對象使用。對於對象內存的釋放就是垃圾回收機制,也叫做gc,對於java開發者來說gc是一個雙刃劍。
c的垃圾回收是人工的,工作量大,但是可控性高。
java是自動化的,但是可控性很差,甚至有時會出現內存溢出的情況,
內存溢出也就是jvm分配的內存中對象過多,超出了最大可分配內存的大小。
提到java的垃圾回收機制就不得不提一個方法:
System.gc()用於調用垃圾收集器,在調用時,垃圾收集器將運行以回收未使用的內存空間。它將嘗試釋放被丟棄對象佔用的內存。
然而System.gc()調用附帶一個免責聲明,無法保證對垃圾收集器的調用。
所以System.gc()並不能說是完美主動進行了垃圾回收。
作爲java程序員還是很有必要了解一下gc,這也是面試過程中經常出現的一道題目。
我們從三個角度來理解gc。
1jvm怎麼確定哪些對象應該進行回收
2jvm會在什麼時候進行垃圾回收的動作
3jvm到底是怎麼清楚垃圾對象的
jvm怎麼確定哪些對象應該進行回收
對象是否會被回收的兩個經典算法:引用計數法,和可達性分析算法。
引用計數法
簡單的來說就是判斷對象的引用數量。實現方式:給對象共添加一個引用計數器,每當有引用對他進行引用時,計數器的值就加1,當引用失效,也就是不在執行此對象是,他的計數器的值就減1,若某一個對象的計數器的值爲0,那麼表示這個對象沒有人對他進行引用,也就是意味着是一個失效的垃圾對象,就會被gc進行回收。
但是這種簡單的算法在當前的jvm中並沒有採用,原因是他並不能解決對象之間循環引用的問題。
假設有A和B兩個對象之間互相引用,也就是說A對象中的一個屬性是B,B中的一個屬性時A,這種情況下由於他們的相互引用,從而是垃圾回收機制無法識別。
因爲引用計數法的缺點有引入了可達性分析算法,通過判斷對象的引用鏈是否可達來決定對象是否可以被回收。可達性分析算法是從離散數學中的圖論引入的,程序把所有的引用關係看作一張圖,通過一系列的名爲GC Roots的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連(就是從 GC Roots 到這個對象不可達)時,則證明此對象是不可用的。
如圖:
二在確定了哪些對象可以被回收之後,jvm會在什麼時候進行回收
1會在cpu空閒的時候自動進行回收
2在堆內存存儲滿了之後
3主動調用System.gc()後嘗試進行回收
三如何回收
三如何回收
如何回收說的也就是垃圾收集的算法。
算法又有四個:標記-清除算法,複製算法,標記-整理算法,分代收集算法.
1 標記-清除算法。
這是最基礎的一種算法,分爲兩個步驟,第一個步驟就是標記,也就是標記處所有需要回收的對象,標記完成後就進行統一的回收掉哪些帶有標記的對象。這種算法優點是簡單,缺點是效率問題,還有一個最大的缺點是空間問題,標記清除之後會產生大量不連續的內存碎片,當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而造成內存空間浪費。
執行如圖:
2複製算法。
複製將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況。只是這種算法的代價是將內存縮小爲原來的一半。
複製算法的執行過程如圖:
複製收集算法在對象存活率較高時就要執行較多的複製操作,效率將會變低。更關鍵的是,浪費了一半的空間。
標記-整理算法:
標記整理算法與標記清除算法很相似,但最顯著的區別是:標記清除算法僅對不存活的對象進行處理,剩餘存活對象不做任何處理,造成內存碎片;而標記整理算法不僅對不存活對象進行處理清除,還對剩餘的存活對象進行整理,重新整理,因此其不會產生內存碎片。
標記整理算法的作用示意圖如下:
分代收集算法:
分代收集算法是一種比較智能的算法,也是現在jvm使用最多的一種算法,他本身其實不是一個新的算法,而是他會在具體的場景自動選擇以上三種算法進行垃圾對象回收。
那麼現在的重點就是分代收集算法中說的自動根據具體場景進行選擇。這個具體場景到底是什麼場景。
場景其實指的是針對jvm的哪一個區域,1.7之前jvm把內存分爲三個區域:新生代,老年代,永久代。
瞭解過場景之後再結合分代收集算法得出結論:
1、在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法。只需要付出少量存活對象的複製成本就可以完成收集。
2、老年代中因爲對象存活率高、沒有額外空間對他進行分配擔保,就必須用標記-清除或者標記-整理。
總結:
注意:
在jdk8的時候java廢棄了永久代,但是並不意味着我們以上的結論失效,因爲java提供了與永久代類似的叫做“元空間”的技術。
廢棄永久代的原因:由於永久代內存經常不夠用或發生內存泄露,爆出異常java.lang.OutOfMemoryErroy。元空間的本質和永久代類似。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。也就是不侷限與jvm可以使用系統的內存。理論上取決於32位/64位系統可虛擬的內存大小。