標籤:引入 gif 程式 dex java程式員 alt media 垃圾收集 複製演算法
任何語言在運行過程中都會建立對象,也就意味著需要在記憶體中為這些對象在記憶體中分配空間,在這些對象失去使用的意義的時候,需要釋放掉這些內容,保證記憶體能夠提供給新的對象使用。對於對象記憶體的釋放就是記憶體回收機制,也叫做gc,對於java開發人員來說gc是一個雙刃劍 我們這裡找了兩張搞笑圖片分別來表示c語言的記憶體回收和 java的記憶體回收。 注意:並不是說誰好誰壞,只是一個調侃圖。
c語言:
java語言:
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位系統可虛擬記憶體大小。
java中的記憶體回收機