大家都知道 java 的記憶體回收機制,java有自己的記憶體回收行程來自動回收垃圾。本人對於記憶體回收機制以前也就知道java的記憶體回收行程是自動回收垃圾的,有這麼回事,知道有finalize和system這兩個方法而已,別人都跟我說,你知道java虛擬機器有記憶體回收這回事就可以了,你不用操心這個的,我也一直深信不疑,並感歎java 虛擬機器真神奇。直到我對 java 的理解逐漸加深後,才發現並不是那麼回事。
下面說個小故事:有一個小土豪家裡請了一個保姆每天打掃衛生,但不知道這個保姆會什麼時候來; java的記憶體回收行程就相當於我們請的這個保姆,它會清理垃圾但你無法控制他什麼時候來清理。
我們要弄清記憶體回收機制要弄清楚以下三個問題:
1.什麼樣的垃圾需要被回收。
2.什麼時候進行記憶體回收。
3.怎樣回收這些垃圾。
1如何判斷對象變成了垃圾。
java 中有一個演算法叫做可達性演算法,這個演算法的思路是把一個 “GC Roots” 的對象作為起始點,從這些節點往下搜尋,搜尋的路徑稱為引用鏈,當一個對象到 GC Roots 之間沒有任何引用鏈相連,(也就是從 GC Roots 到這個對象不可達)則可以證明該對象是停用,就會被當做記憶體回收的對象。
GC Roots 不指向了的對象就搜尋要被回收的對象 任何對象被記憶體回收時都會至少經曆兩次標記的過程。
GC Roots不指向該對象時,標記一次,標記這次時該對象還沒有真正被回收。
2.什麼時候進行記憶體回收。
在標記完第一次後,會判斷該對象是否有必要執行finalize()方法,finalize方法會第二次標記該對象,一旦被第二次標記後,該對象就肯定會被記憶體回收行程回收掉了。
如果有必要的話就會把這些對象放到一個叫做F-Queue的隊列中,然後虛擬機器會自動建立一個優先順序比較低的線程去執行記憶體回收的方法,所以這裡可以判斷,在程式空閑時記憶體回收行程會進行記憶體回收。
這裡詳細說一下finalize方法,該方法是屬於object類的,它本身是一個空方法。特殊情況如果 finalize中該對象又指向了某個類變數,該對象就會逃過一劫不會被記憶體回收掉了。
不記得是在什麼地方看到過說可以在finalize方法中釋放程式資源。這種方法完全是不可取的,因為釋放資源的代碼放在 finally 代碼塊中去執行好得多,因為finally代碼塊收程式員控制,而finalize方法不被程式員所控。
3.怎樣回收這些垃圾。
運行finalize方法,然後開始記憶體回收。
當前使用的虛擬機器的垃圾收集都採用 分代收集 演算法, 根據對象存貨周期的不同將記憶體劃分為幾塊。java堆分為:新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集演算法;
在新生代中每次垃圾收集時都有大批的對象死去,只有少量的存活,那就選用複製式演算法來進行回收;
而老年代中因為對象存活率高,沒有額外空間對它進行分配擔保,就必須使用標記式演算法來進行回收;
複製式:把記憶體分為大小相等的兩塊,每次只使用其中的一塊,當這一塊的記憶體用完了,就將還存活著的對象複製到另外一塊儲存空間去,然後把已使用過的記憶體空間一次全部清理掉。
(感覺這裡也可以判斷:當記憶體快滿了的時候就會進行記憶體回收了)
標記式:分為 標記---清除演算法 和 標記---整理演算法
標記清除:標記出所有要回收的對象,然後統一回收;這種方式效率低,還會產生大量的不連續記憶體片段,很少使用了;
標記整理:標記出所有要回收的對象,然後讓所有存活著的對象往一端移動,然後直接清理另外一端的垃圾;
記憶體回收的過程中也進行了記憶體中的片段 清理工作;
要是有對記憶體方面還不是特別理解的小夥伴可以換一個方式來理解一下:
凡是主人不要了的東西統統都是垃圾。主人這裡作為 GC Roots 對象 他不想跟某些東西產生關聯,這些東西就自然的變成了垃圾。
保姆把家裡所有的物品搜尋一遍,看到新買的飲料還沒有開蓋,看到新鮮的橘子還散發著清新的香味,作為記憶體回收行程的保姆很清楚這些東西都是新的不需要記憶體回收,但是保姆會把喝完了的飲料瓶、吃完了後剩下的橘子皮收集起來,(飲料瓶可以裝水,橘子皮還可以泡茶,還有再利用功能就好比 GC Roots指向不到的對象只要重新指向了其他類對象的屬性就會被再次啟用)問主人是不是真的要把這些東西處理掉。 如果主人說是的話就執行finalize方法 ,進行二次標記,在某一個時間段進行垃圾清理工作。
垃圾多的時候把所有東西都放在一個袋子裡,把幾個不丟的東西拿出來,然後把整個袋子丟掉,這就叫複製式。
垃圾少的時候也把所有東西都放在一個袋子裡,把幾個要丟的垃圾拿出來丟掉,這就叫標記式。
總結:
1.GC Roots 指向不了的對象就搜尋要被回收的對象 。
2.在程式空閑時記憶體回收行程會進行記憶體回收。在記憶體快滿的時候要進行記憶體回收。
3.執行finalize方法後會開始記憶體回收,新生代進行複製式回收,老年代進行標記式回收。
看到這裡大家應該稍微明白為什麼要注意記憶體回收了細節了吧。
1.因為執行記憶體回收方法的線程優先順序很低,如果亂new對象,記憶體空間快滿了的時候記憶體回收行程會強行進行記憶體回收。
記憶體回收行程工作時是要消耗系統資源的,這時勢必會影響其他線程的運行,影響程式的效率。
2.讓程式員有警惕的意識,能用StringBuffer 修改字元就不要 去用String 創造好幾個對象再去拼接而佔用記憶體。
能用Person p = new Person(); 通過 引用變數 p 去調用方法 就不要用匿名的對象 new Person()去調用。
3.隨著程式的訪問量增長,程式的底層是肯定要最佳化的,記憶體空間也是很重要的一個地方~