android內從泄漏

來源:互聯網
上載者:User

轉自:http://www.cnblogs.com/lbeing/archive/2010/09/29/1838858.htmlandroid記憶體泄露的問題

最近寫的一個程式中記憶體會不斷增加,網上尋找相關資料。整理如下:

0:原因:Java的記憶體管理與記憶體泄露(http://immortal.5d6d.com/thread-36-1-1.html)
Java記憶體流失是每個Java程式員都會遇到的問題,程式在本地運行一切正常,可是布署到遠端就會出現記憶體無限制的增長,最後系統癱瘓,那麼如何最快最好的檢測程式的穩定性,防止系統崩盤,作者用自已的親身經曆與各位網友分享解決這些問題的辦法。
作為Internet最流行的程式設計語言之一,Java現正非常流行。我們的網路應用程式就主要採用Java語言開發,大體上分為用戶端、伺服器和資料庫三個層次。在進入測試過程中,我們發現有一個程式模組系統記憶體和CPU資源消耗急劇增加,持續增長到出現java.lang.OutOfMemoryError為止。經過分析Java記憶體流失是破壞系統的主要因素。這裡與大家分享我們在開發過程中遇到的Java記憶體流失的檢測和處理解決過程. 

本文先介紹Java的記憶體管理,以及導致Java記憶體泄露的原因。

一. Java是如何管理記憶體 

為了判斷Java中是否有記憶體泄露,我們首先必須瞭解Java是如何管理記憶體的。Java的記憶體管理就是對象的分配和釋放問題。在Java中,記憶體的分配是由程式完成的,而記憶體的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程式員不需要通過調用函數來釋放記憶體,但它只能回收無用並且不再被其它對象引用的那些對象所佔用的空間。 

Java的記憶體記憶體回收機制是從程式的主要運行對象開始檢查引用鏈,當遍曆一遍後發現沒有被引用的孤立對象就作為記憶體回收。GC為了能夠正確釋放對象,必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都需要進行監控。監視對象狀態是為了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。 

在Java中,這些無用的對象都由GC負責回收,因此程式員不需要考慮這部分的記憶體泄露。雖然,我們有幾個函數可以訪問GC,例如運行GC的函數System.gc(),但是根據Java語言規範定義,該函數不保證JVM的垃圾收集器一定會執行。因為不同的JVM實現者可能使用不同的演算法管理GC。通常GC的線程的優先順序別較低。JVM調用GC的策略也有很多種,有的是記憶體使用量到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心這些。 

二. 什麼是Java中的記憶體泄露 

導致記憶體流失主要的原因是,先前申請了記憶體空間而忘記了釋放。如果程式中存在對無用對象的引用,那麼這些對象就會駐留記憶體,消耗記憶體,因為無法讓記憶體回收行程GC驗證這些對象是否不再需要。如果存在對象的引用,這個對象就被定義為"有效活動",同時不會被釋放。要確定對象所佔記憶體將被回收,我們就要務必確認該對象不再會被使用。典型的做法就是把對象資料成員設為null或者從集合中移除該對象。但當局部變數不需要時,不需明顯的設為null,因為一個方法執行完畢時,這些引用會自動被清理。 

在Java中,記憶體流失就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是有被引用的,即在有向樹形圖中,存在樹枝通路可以與其相連;其次,這些對象是無用的,即程式以後不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的記憶體流失,這些對象不會被GC所回收,然而它卻佔用記憶體。 

這裡引用一個常看到的例子,在下面的代碼中,迴圈申請Object對象,並將所申請的對象放入一個Vector中,如果僅僅釋放對象本身,但因為Vector仍然引用該對象,所以這個對象對GC來說是不可回收的。因此,如果對象加入到Vector後,還必須從Vector中刪除,最簡單的方法就是將Vector對象設定為null。 

Vector v = new Vector(10);      for (int i = 1; i < 100; i++)      {       Object o = new Object();       v.add(o);       o = null;      }//此時,所有的Object對象都沒有被釋放,因為變數v引用這些對象。     實際上這些對象已經是無用的,但還被引用,GC就無能為力了(事實上GC認為它還有用),這一點是導致記憶體流失最重要的原因。 再引用另一個例子來說明Java的記憶體流失。假設有一個日誌類Logger,其提供一個靜態log(String
msg),任何其它類都可以調用Logger.Log(message)來將message的內容記錄到系統的記錄檔中。

Logger類有一個類型為HashMap的靜態變數temp,每次在執行log(message)的時候,都首先將message的值寫入temp中(以當前線程+目前時間為鍵),在退出之前再從temp中將以當前線程和目前時間為鍵的條目刪除。注意,這裡目前時間是不斷變化的,所以log在退出之前執行刪除條目的操作並不能刪除執行之初寫入的條目。這樣,任何一個作為參數傳給log的字串最終由於被Logger的靜態變數temp引用,而無法得到回收,這種對象保持就是我們所說的Java記憶體流失。 總的來說,記憶體管理中的記憶體流失產生的主要原因:保留下來卻永遠不再使用的對象引用。

 

1:尋找記憶體泄露的方法。


與C++的記憶體不同,C++的記憶體泄露是由於分配了記憶體給某程式但是又沒有回收造成的。Java的記憶體泄露則是引用了一些垃圾對象,意思就是說程式引用了某些對象,但是又從來沒有使用過。

Jave中的引用分為3種:

強引用:引用為空白的時候,Java的記憶體回收行程會處理。一般來說自己寫的程式大部分都是強引用。

軟引用:堆記憶體不夠的時候,Java的記憶體回收行程會處理這類引用。

弱引用:Jave的記憶體回收行程每次都會回收這類引用。

如何用MAT來分析,前提是Android開發與測試的工具安裝完整,SDK,Eclipse:

1.開啟Eclipse

2.選擇 Help->Install New Software;

3.在Work with中添加網站:http://download.eclipse.org/mat/1.0/update-site/(這個地址可能會變化,但是新的地址可以在官方網站上找到:http://www.eclipse.org/mat/downloads.php )

4.產生.hprof檔案:插入SD卡(Android機器很多程式都需要插入SD卡),並將裝置串連到PC,在Eclipse中的DDMS中選擇要測試的進程,然後點擊Update Heap 和Dump HPROF file兩個Button。

.hprof 檔案會自動儲存在SD卡上,把 .hprof 檔案拷貝到PC上的/ android-sdk-windows/tools目錄下。這個由DDMS產生的檔案不能直接在MAT開啟,需要轉換。

運行cmd開啟命令列,cd到/ android-sdk-windows/tools所在目錄,並輸入命令hprof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof為原始檔案,yyyyy.hprof為轉換過後的檔案。轉換過後的檔案自動放在android-sdk-windows/tools 目錄下。

OK,到此為止,.hprof檔案處理完畢,可以用來分析記憶體泄露情況了。

5.開啟MAT:

在Eclipse中點擊Windows->Open Perspective->Other->Memory Analysis

6.匯入.hprof檔案

在MAT中點擊 File->Open File,瀏覽到剛剛轉換而得到的.hprof檔案,並Cancel掉自動產生報告,點擊Dominator Tree,並按Package分組,選擇自己所定義的Package 類點右鍵,在快顯功能表中選擇List objects->With incoming references。

這時會列出所有可疑類,右鍵點擊某一項,並選擇Path to GC Roots->exclude weak/soft references,會進一步篩選出跟程式相關的所有有記憶體泄露的類。據此,可以追蹤到代碼中的某一個產生泄露的類。

2:相關知識

Android將進程分為六大類:

前台進程(foreground):目前正在螢幕上顯示的進程和一些系統進程。舉例來說,Dialer Storage,Google Search等系統進程就是前台進程;再舉例來說,當你運行一個程式,如瀏覽器,當瀏覽器介面在前台顯示時,瀏覽器屬於前台進程(foreground),但一旦你按home回到主介面,瀏覽器就變成了背景程式(background)。我們最不希望終止的進程就是前台進程。
  可見進程(visible):可見進程是一些不再前台,但使用者依然可見的進程,舉個例來說:widget、IME等,都屬於visible。這部分進程雖然不在前台,但與我們的使用也密切相關,我們也不希望它們被終止(你肯定不希望時鐘、天氣,新聞等widget被終止,那它們將無法同步,你也不希望IME被終止,否則你每次輸入時都需要重新啟動IME)
  次要服務(secondary server):目前正在啟動並執行一些服務(主要服務,如撥號等,是不可能被進程管理終止的,故這裡只談次要服務),舉例來說:Google企業套件,Gmail內部儲存,連絡人內部儲存等。這部分服務雖然屬於次要服務,但很一些系統功能依然息息相關,我們時常需要用到它們,所以也太希望他們被終止。
  後台進程(hidden):雖然作者用了hidden這個詞,但實際即是後台進程(background),就是我們通常意義上理解的啟動後被切換到背景進程,如瀏覽器,閱讀器等。當程式顯示在螢幕上時,他所啟動並執行進程即為前台進程(foreground),一旦我們按home返回主介面(注意是按home,不是按back),程式就駐留在後台,成為後台進程(background)。後台進程的管理原則有多種:有較為積極的方式,一旦程式到達後台立即終止,這種方式會提高程式的運行速度,但無法加速程式的再次啟動;也有較消極的方式,儘可能多的保留背景程式,雖然可能會影響到單個程式的運行速度,但在再次啟動已啟動的程式時,速度會有所提升。這裡就需要使用者根據自己的使用習慣找到一個平衡點
  內容供應節點(content provider):沒有程式實體,進提供內容供別的程式去用的,比如日曆供應節點,郵件供應節點等。在終止進程時,這類程式應該有較高的優先權
  空進程(empty):沒有任何東西在內啟動並執行進程,有些程式,比如BTE,在程式退出後,依然會在進程中駐留一個空進程,這個進程裡沒有任何資料在運行,作用往往是提高該程式下次的啟動速度或者記錄程式的一些曆史資訊。這部分進程無疑是應該最先終止的。系統會對進程的重要性進行評估,並將重要性以“oom_adj”這個數值表示出來,賦予各個進程;(系統會根據“oom_adj”來判斷需要結束哪些進程,一般來說,“oom_adj”的值越大,該進程被系統選中終止的可能就越高)
  前景程式的“oom_adj”值為0,這意味著它不會被系統終止,一旦它不可訪問後,會獲得個更高的“oom_adj”,作者推測“oom_adj”的值是根據軟體在LRU列表中的位置所決定的; 
Android不同於Linux,有一套自己獨特的進程管理模組,這個模組有更強的可定製性,可根據“oom_adj”值的範圍來決定進程管理原則,比如可以設定“當記憶體小於X時,結束“oom_adj”大於Y的進程”。這給了進程管理指令碼的編寫以更多的選擇。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.