對於一些手機內存概念的思考、深刻理解java的finalize,對於內存優化的小總結

最近在對手機性能作了一些小概念總結,方便對於本身的我的思考java

Shallow Size:程序員

對象自己佔用的內存空間,不包含其引用的對象,但在JAVA中除基本類型外,一切均爲對象,也就是說持有的一直爲對象的引用,如String類型對象,它主要包含3個int成員(3*4B)、1個char[]成員(1*4B)以及一個對象頭(8B),儘管char[]可能指向一大塊字符,但String對象裏只有一個引用所佔4B的空間,所以String類型對象的Shallow Size就是12B+4B+8B = 24B。安全

 

Retained Size:ide

對象自己的Shallow Size + 對象能直接或間接訪問到的對象的Shallow Size,也就是說Retained Size就是該對象被gc以後所能回收內存的總和。性能

 

爲了更好地理解Retained Size,不妨以圖1加以說明,圖中每一個節點對應內存中的一個對象,但注意這裏有個特殊的節點GC Roots(詳見註解),它也是Reference chain的起點。在圖3中,從對象obj1入手,可知經過obj1能夠直接或間接訪問的對象爲藍色節點,而左圖中obj3是GC Roots可達的,也就是說斷掉obj1到obj3的Reference chain後,obj3還有GC Roots的Reference chain,於是在gc時obj1被回收也不會回收obj3,所以在左圖obj1的obj1的Retained Size = obj1 + obj2 + obj4 的shallow size,而右圖obj1的Retained Size = obj1 + obj2 + obj4 +obj3 的shallow size.總之,Retained size是一個總體度量,能反映內存結構和對象圖的依賴關係,還能夠找到根節點。測試

 

圖1 Retained Size示例圖this

注:GC Roots特指垃圾收集器對象,JVM GC時會回收那些不是GC roots且沒有被GC roots引用的對象。JVM對內存中對象進行回收,主要是經過GC Roots Tracing來辨別對象是否在使用,進而進行回收。也就是說經過一系列名爲「GC Roots」的對象做爲起始點,從節點向下搜索,搜索過的路徑稱之爲Reference Chain,當一個對象到GC Roots沒有任何Reference Chain相連時,則代表此對象不可用,GC時將被回收(該流程的示例圖見圖2)。spa

 

圖2.GC Roots Tracing回收機制.net

 

  Heap Size: 線程

堆的大小,當資源增長,當前堆的空間不夠時,系統會增長堆的大小,若超過上限(如64M,閾值視平臺而定)則會被殺掉

 Allocated:

堆中已分配的大小,即App應用實際佔用的內存大小,資源回收後,此項數據會變小。

關於gc過程,咱們又要看一個finalize方法,能夠更好的理解gc

java的GC只負責內存相關的清理,全部其它資源的清理必須由程序員手工完成。要否則會引發資源泄露,有可能致使程序崩潰。 

2 調用GC並不保證GC實際執行。 

3 finalize拋出的未捕獲異常只會致使該對象的finalize執行退出。 

4 用戶能夠本身調用對象的finalize方法,可是這種調用是正常的方法調用,和對象的銷燬過程無關。 

5 JVM保證在一個對象所佔用的內存被回收以前,若是它實現了finalize方法,則該方法必定會被調用。Object的默認finalize什麼都不作,爲了效率,GC能夠認爲一個什麼都不作的finalize不存在。 

6 對象的finalize調用鏈和clone調用鏈同樣,必須手工構造。 
如 

Java代碼  收藏代碼

  1. protected void finalize() throws Throwable {  
  2.     super.finalize();  
  3. }  




對象的銷燬過程 

在對象的銷燬過程當中,按照對象的finalize的執行狀況,能夠分爲如下幾種,系統會記錄對象的對應狀態: 
unfinalized 沒有執行finalize,系統也不許備執行。 
finalizable 能夠執行finalize了,系統會在隨後的某個時間執行finalize。 
finalized 該對象的finalize已經被執行了。 

GC怎麼來保持對finalizable的對象的追蹤呢。GC有一個Queue,叫作F-Queue,全部對象在變爲finalizable的時候會加入到該Queue,而後等待GC執行它的finalize方法。 

這時咱們引入了對對象的另一種記錄分類,系統能夠檢查到一個對象屬於哪種。 
reachable 從活動的對象引用鏈能夠到達的對象。包括全部線程當前棧的局部變量,全部的靜態變量等等。 
finalizer-reachable 除了reachable外,從F-Queue能夠經過引用到達的對象。 
unreachable 其它的對象。 

來看看對象的狀態轉換圖。 


好大,好暈,慢慢看。 

1 首先,全部的對象都是從Reachable+Unfinalized走向死亡之路的。 

2 當從當前活動集到對象不可達時,對象能夠從Reachable狀態變到F-Reachable或者Unreachable狀態。 

3 當對象爲非Reachable+Unfinalized時,GC會把它移入F-Queue,狀態變爲F-Reachable+Finalizable。 

4 好了,關鍵的來了,任什麼時候候,GC均可以從F-Queue中拿到一個Finalizable的對象,標記它爲Finalized,而後執行它的finalize方法,因爲該對象在這個線程中又可達了,因而該對象變成Reachable了(而且Finalized)。而finalize方法執行時,又有可能把其它的F-Reachable的對象變爲一個Reachable的,這個叫作對象再生。 

5 當一個對象在Unreachable+Unfinalized時,若是該對象使用的是默認的Object的finalize,或者雖然重寫了,可是新的實現什麼也不幹。爲了性能,GC能夠把該對象之間變到Reclaimed狀態直接銷燬,而不用加入到F-Queue等待GC作進一步處理。 

6 從狀態圖看出,無論怎麼折騰,任意一個對象的finalize只至多執行一次,一旦對象變爲Finalized,就怎麼也不會在回到F-Queue去了。固然沒有機會再執行finalize了。 

7 當對象處於Unreachable+Finalized時,該對象離真正的死亡不遠了。GC能夠安全的回收該對象的內存了。進入Reclaimed。 


對象重生的例子 

Java代碼  收藏代碼

  1. class C {  
  2.     static A a;  
  3. }  
  4.   
  5. class A {  
  6.     B b;  
  7.   
  8.     public A(B b) {  
  9.         this.b = b;  
  10.     }  
  11.   
  12.     @Override  
  13.     public void finalize() {  
  14.         System.out.println("A finalize");  
  15.         C.a = this;  
  16.     }  
  17. }  
  18.   
  19. class B {  
  20.     String name;  
  21.     int age;  
  22.   
  23.     public B(String name, int age) {  
  24.         this.name = name;  
  25.         this.age = age;  
  26.     }  
  27.   
  28.     @Override  
  29.     public void finalize() {  
  30.         System.out.println("B finalize");  
  31.     }  
  32.   
  33.     @Override  
  34.     public String toString() {  
  35.         return name + " is " + age;  
  36.     }  
  37. }  
  38.   
  39. public class Main {  
  40.     public static void main(String[] args) throws Exception {  
  41.         A a = new A(new B("allen", 20));  
  42.         a = null;  
  43.   
  44.         System.gc();  
  45.         Thread.sleep(5000);  
  46.         System.out.println(C.a.b);  
  47.     }  
  48. }  



期待輸出 

Java代碼  收藏代碼

  1. A finalize  
  2. B finalize  
  3. allen is 20  


可是有可能失敗,源於GC的不肯定性以及時序問題,多跑幾回應該能夠有成功的。詳細解釋見文末的參考文檔。 

對象的finalize的執行順序 

全部finalizable的對象的finalize的執行是不肯定的,既不肯定由哪一個線程執行,也不肯定執行的順序。 
考慮如下狀況就明白爲何了,實例a,b,c是一組相互循環引用的finalizable對象。 

什麼時候及如何使用finalize 

從以上的分析得出,如下結論。 
1 最重要的,儘可能不要用finalize,太複雜了,仍是讓系統照管比較好。能夠定義其它的方法來釋放非內存資源。 
2 若是用,儘可能簡單。 
3 若是用,避免對象再生,這個是本身給本身找麻煩。 
4 能夠用來保護非內存資源被釋放。即便咱們定義了其它的方法來釋放非內存資源,可是其它人未必會調用該方法來釋放。在finalize裏面能夠檢查一下,若是沒有釋放就釋放好了,晚釋放總比不釋放好。 
5 即便對象的finalize已經運行了,不能保證該對象被銷燬。要實現一些保證對象完全被銷燬時的動做,只能依賴於java.lang.ref裏面的類和GC交互了。 

參考 

關於引用類型,GC,finalize的相互交互能夠參考ReferenceQueue GC finalize Reference 測試及相關問題

相關文章
相關標籤/搜索