轉自:http://blog.csdn.net/a396901990/article/details/38904543
寫在最前:
本文的思路主要借鑑了2014年AnDevCon開發者大會的一個演講PPT,加上把網上搜集的各種內存零散知識點進行彙總、挑選、簡化後整理而成。
所以我將本文定義爲一個工具類的文章,如果你在ANDROID開發中遇到關於內存問題,或者馬上要參加面試,或者就是單純的學習或複習一下內存相關知識,都歡迎閱讀。(本文最後我會盡量列出所參考的文章)。
OOM:
內存泄露可以引發很多的問題:
1.程序卡頓,響應速度慢(內存佔用高時JVM虛擬機會頻繁觸發GC)
2.莫名消失(當你的程序所佔內存越大,它在後臺的時候就越可能被幹掉。反之內存佔用越小,在後臺存在的時間就越長)
3.直接崩潰(OutOfMemoryError)
ANDROID內存面臨的問題:
1.有限的堆內存,原始只有16M
2.內存大小消耗等根據設備,操作系統等級,屏幕尺寸的不同而不同
3.程序不能直接控制
4.支持後臺多任務處理(multitasking)
5.運行在虛擬機之上
5R:
本文主要通過如下的5R方法來對ANDROID內存進行優化:
1.Reckon(計算)
首先需要知道你的app所消耗內存的情況,知己知彼才能百戰不殆
2.Reduce(減少)
消耗更少的資源
3.Reuse(重用)
當第一次使用完以後,儘量給其他的使用
5.Recycle(回收)
回收資源
4.Review(檢查)
回顧檢查你的程序,看看設計或代碼有什麼不合理的地方。
內存簡介,Reckon(計算):
關於內存簡介,和Reckon的內容請看:ANDROID內存優化(大彙總——上)
Reduce(減少) ,Reuse(重用):
關於Reduce,和Reuse的內容請看:ANDROID內存優化(大彙總——中)
Recycle(回收):
Recycle(回收),回收可以說是在內存使用中最重要的部分。因爲內存空間有限,無論你如何優化,如何節省內存總有用完的時候。而回收的意義就在於去清理和釋放那些已經閒置,廢棄不再使用的內存資源和內存空間。
因爲在Java中有垃圾回收(GC)機制,所以我們平時都不會太關注它,下面就來簡單的介紹一下回收機制:
垃圾回收(GC):
Java垃圾回收器:
在C,C++或其他程序設計語言中,資源或內存都必須由程序員自行聲明產生和回收,否則其中的資源將消耗,造成資源的浪費甚至崩潰。但手工回收內存往往是一項複雜而艱鉅的工作。
於是,Java技術提供了一個系統級的線程,即垃圾收集器線程(Garbage Collection Thread),來跟蹤每一塊分配出去的內存空間,當Java 虛擬機(Java Virtual Machine)處於空閒循環時,垃圾收集器線程會自動檢查每一快分配出去的內存空間,然後自動回收每一快可以回收的無用的內存塊。
作用:
1.清除不用的對象來釋放內存:
採用一種動態存儲管理技術,它自動地釋放不再被程序引用的對象,按照特定的垃圾收集算法來實現資源自動回收的功能。當一個對象不再被引用的時候,內存回收它佔領的空間,以便空間被後來的新對象使用。
2.消除堆內存空間的碎片:
由於創建對象和垃圾收集器釋放丟棄對象所佔的內存空間,內存會出現碎片。碎片是分配給對象的內存塊之間的空閒內存洞。碎片整理將所佔用的堆內存移到堆的一端,JVM將整理出的內存分配給新的對象。
垃圾回收器優點:
1.減輕編程的負擔,提高效率:
使程序員從手工回收內存空間的繁重工作中解脫了出來,因爲在沒有垃圾收集機制的時候,可能要花許多時間來解決一個難懂的存儲器問題。在用Java語言編程的時候,靠垃圾收集機制可大大縮短時間。
2.它保護程序的完整性:
因此垃圾收集是Java語言安全性策略的一個重要部份。
垃圾回收器缺點:
1.佔用資源時間:
Java虛擬機必須追蹤運行程序中有用的對象,
而且最終釋放沒用的對象。這一個過程需要花費處理器的時間。
2.不可預知:
垃圾收集器線程雖然是作爲低優先級的線程運行,但在系統可用內存量過低的時候,它可能會突發地執行來挽救內存資源。當然其執行與否也是不可預知的。
3.不確定性:
不能保證一個無用的對象一定會被垃圾收集器收集,也不能保證垃圾收集器在一段Java語言代碼中一定會執行。
同樣也沒有辦法預知在一組均符合垃圾收集器收集標準的對象中,哪一個會被首先收集。
4.不可操作
垃圾收集器不可以被強制執行,但程序員可以通過調用System. gc方法來建議執行垃圾收集器。
比較古老的回收算法。原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集計數爲0的對象。此算法最致命的是無法處理循環引用的問題。
此算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。
此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。次算法每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不過出現“碎片”問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內存空間。
此算法結合了 “標記-清除”和“複製”兩個算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象 “壓縮”到堆的其中一塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“複製”算法的空間問題。
實施垃圾回收算法,即:在應用進行的同時進行垃圾回收。不知道什麼原因JDK5.0中的收集器沒有使用這種算法的。
基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。
finalize():
每一個對象都有一個finalize方法,這個方法是從Object類繼承來的。
當垃圾回收確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。
Java 技術允許使用finalize方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。一旦垃圾回收器準備好釋放對象佔用的空間,將首先調用其finalize()方法,並且在下一次垃圾回收動作發生時,纔會真正回收對象佔用的內存。
簡單的說finalize方法是在垃圾收集器刪除對象之前對這個對象調用的
System.gc():
我們可以調用System.gc方法,建議虛擬機進行垃圾回收工作(注意,是建議,但虛擬機會不會這樣幹,我們也無法預知!)
下面來看一個例子來了解finalize()和System.gc()的使用:
- public class TestGC {
- public TestGC() {}
- //當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。
- protected void finalize() {
- System.out.println("我已經被垃圾回收器回收了...");
- }
- public static void main(String [] args) {
- TestGC gc = new TestGC();
- gc = null;
- // 建議虛擬機進行垃圾回收工作
- System.gc();
- }
- }
答案是:不一定!
因爲無論是設置gc的引用爲null還是調用System.gc()方法都只是"建議"垃圾回收器進行垃圾回收,但是最終所有權還在垃圾回收器手中,它會不會進行回收我們無法預知!
垃圾回收面試題:
最後通過網上找到的3道面試題來結束垃圾回收的內容。
面試題一:
- 1.fobj = new Object ( ) ;
- 2.fobj. Method ( ) ;
- 3.fobj = new Object ( ) ;
- 4.fobj. Method ( ) ;
問:這段代碼中,第幾行的fobj 符合垃圾收集器的收集標準?
答:第3行。因爲第3行的fobj被賦了新值,產生了一個新的對象,即換了一塊新的內存空間,也相當於爲第1行中的fobj賦了null值。這種類型的題是最簡單的。
面試題二:
- 1.Object sobj = new Object ( ) ;
- 2.Object sobj = null ;
- 3.Object sobj = new Object ( ) ;
- 4.sobj = new Object ( ) ;
答:第2行和第4行。因爲第2行爲sobj賦值爲null,所以在此第1行的sobj符合垃圾收集器的收集標準。而第4行相當於爲sobj賦值爲null,所以在此第3行的sobj也符合垃圾收集器的收集標準。
如果有一個對象的句柄a,且你把a作爲某個構造器的參數,即 new Constructor ( a )的時候,即使你給a賦值爲null,a也不符合垃圾收集器的收集標準。直到由上面構造器構造的新對象被賦空值時,a纔可以被垃圾收集器收集。
面試題三:
- 1.Object aobj = new Object ( ) ;
- 2.Object bobj = new Object ( ) ;
- 3.Object cobj = new Object ( ) ;
- 4.aobj = bobj;
- 5.aobj = cobj;
- 6.cobj = null;
- 7.aobj = null;
答:第4,7行。注意這類題型是認證考試中可能遇到的最難題型了。
行1-3:分別創建了Object類的三個對象:aobj,bobj,cobj
行4:此時對象aobj的句柄指向bobj,原來aojb指向的對象已經沒有任何引用或變量指向,這時,就符合回收標準。
行5:此時對象aobj的句柄指向cobj,所以該行的執行不能使aobj符合垃圾收集器的收集標準。
行6:此時仍沒有任何一個對象符合垃圾收集器的收集標準。
行7:對象cobj符合了垃圾收集器的收集標準,因爲cobj的句柄指向單一的地址空間。在第6行的時候,cobj已經被賦值爲null,但由cobj同時還指向了aobj(第5行),所以此時cobj並不符合垃圾收集器的收集標準。而在第7行,aobj所指向的地址空間也被賦予了空值null,這就說明了,由cobj所指向的地址空間已經被完全地賦予了空值。所以此時cobj最終符合了垃圾收集器的收集標準。 但對於aobj和bobj,仍然無法判斷其是否符合收集標準。
總之,在Java語言中,判斷一塊內存空間是否符合垃圾收集器收集的標準只有兩個:
1.給對象賦予了空值null,以下再沒有調用過。
2.給對象賦予了新值,既重新分配了內存空間。
最後再次提醒一下,一塊內存空間符合了垃圾收集器的收集標準,並不意味着這塊內存空間就一定會被垃圾收集器收集。
資源的回收:
剛纔講了一堆理論的東西,下面來點實際能用上的,資源的回收:
Thread(線程)回收:
線程中涉及的任何東西GC都不能回收(Anything reachable by a thread cannot be GC'd ),所以線程很容易造成內存泄露。
如下面代碼所示:
- Thread t = new Thread() {
- public void run() {
- while (true) {
- try {
- Thread.sleep(1000);
- System.out.println("thread is running...");
- } catch (InterruptedException e) {
- }
- }
- }
- };
- t.start();
- t = null;
- System.gc();
最後的結果是線程並不會被回收,它會一直的運行下去。
因爲運行中的線程是稱之爲垃圾回收根(GC Roots)對象的一種,不會被垃圾回收。當垃圾回收器判斷一個對象是否可達,總是使用垃圾回收根對象作爲參考點。
Cursor(遊標)回收:
Cursor是Android查詢數據後得到的一個管理數據集合的類,在使用結束以後。應該保證Cursor佔用的內存被及時的釋放掉,而不是等待GC來處理。並且Android明顯是傾向於編程者手動的將Cursor
所以我們使用Cursor的方式一般如下:
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(uri,null, null,null,null);
- if(cursor != null) {
- cursor.moveToFirst();
- //do something
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- @Override
- protected void onDestroy() {
- if (mAdapter != null && mAdapter.getCurosr() != null) {
- mAdapter.getCursor().close();
- }
- super.onDestroy();
- }
Receiver(接收器)回收
也就是說registerReceiver()和unregisterReceiver()方法一定要成對出現,通常我們可以重寫Activity的onDestory()方法:
- @Override
- protected void onDestroy() {
- this.unregisterReceiver(receiver);
- super.onDestroy();
- }
Stream/File(流/文件)回收:
主要針對各種流,文件資源等等如:
InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap圖片等操作等都應該記得顯示關閉。
和之前介紹的Cursor道理類似,就不多說了。
Review:
Review(回顧,檢查),大家都知道Code Review的重要性。而這裏我說的Review和Code Review差不多,主要目的就是檢查代碼中存在的不合理和可以改進的地方,當然這個Review需要大家自己來做啦。
Code Review(代碼檢查):
Code Review主要檢查代碼中存在的一些不合理或可以改進優化的地方,大家可以參考之前寫的Reduce,Reuse和Recycle都是側重講解這方面的。
UI Review(視圖檢查):
Android對於視圖中控件的佈局渲染等會消耗很多的資源和內存,所以這部分也是我們需要注意的。
如上圖大家可以看到,hierarchyviewer可以非常清楚的看到當前視圖的層級結構,並且可以查看視圖的執行效率(視圖上的小圓點,綠色表示流暢,黃色和紅色次之),所以我們可以很方便的查看哪些view可能會影響我們的性能從而去進一步優化它。
hierarchyviewer還提供另外一種列表式的查看方式,可以查看詳細的屏幕畫面,具體到像素級別的問題都可以通過它發現。
ViewStub標籤
此標籤可以使UI在特殊情況下,直觀效果類似於設置View的不可見性,但是其更大的意義在於被這個標籤所包裹的Views在默認狀態下不會佔用任何內存空間。
include標籤
可以通過這個標籤直接加載外部的xml到當前結構中,是複用UI資源的常用標籤。
merge標籤
它在優化UI結構時起到很重要的作用。目的是通過刪減多餘或者額外的層級,從而優化整個Android Layout的結構。
(注意:靈活運用以上3個標籤可以有效減少視圖層級,具體使用大家可以上網搜搜)
佈局用Java代碼比寫在XML中快
一般情況下對於Android程序佈局往往使用XML文件來編寫,這樣可以提高開發效率,但是考慮到代碼的安全性以及執行效率,可以通過Java代碼執行創建,雖然Android編譯過的XML是二進制的,但是加載XML解析器的效率對於資源佔用還是比較大的,Java處理效率比XML快得多,但是對於一個複雜界面的編寫,可能需要一些套嵌考慮,如果你思維靈活的話,使用Java代碼來佈局你的Android應用程序是一個更好的方法。
重用系統資源:
1. 利用系統定義的id
比如我們有一個定義ListView的xml文件,一般的,我們會寫類似下面的代碼片段。
- <ListView
- android:id="@+id/mylist"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"/>
這裏我們定義了一個ListView,定義它的id是"@+id/mylist"。實際上,如果沒有特別的需求,就可以利用系統定義的id,類似下面的樣子。
- <ListView
- android:id="@android:id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"/>
2. 利用系統的圖片資源
這樣做的好處,一個是美工不需要重複的做一份已有的圖片了,可以節約不少工時;另一個是能保證我們的應用程序的風格與系統一致。
3. 利用系統的字符串資源
如果使用系統的字符串,默認就已經支持多語言環境了。如上述代碼,直接使用了@android:string/yes和@android:string/no,在簡體中文環境下會顯示“確定”和“取消”,在英文環境下會顯示“OK”和“Cancel”。
4. 利用系統的Style
假設佈局文件中有一個TextView,用來顯示窗口的標題,使用中等大小字體。可以使用下面的代碼片段來定義TextView的Style。
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium" />
5. 利用系統的顏色定義
除了上述的各種系統資源以外,還可以使用系統定義好的顏色。在項目中最常用的,就是透明色的使用。
- android:background ="@android:color/transparent"
除了上面介紹的以外還有很多其他Android系統本身自帶的資源,它們在應用中都可以直接使用。具體的,可以進入android-sdk的相應文件夾中去查看。例如:可以進入$android-sdk$\platforms\android-8\data\res,裏面的系統資源就一覽無餘了。
開發者需要花一些時間去熟悉這些資源,特別是圖片資源和各種Style資源,這樣在開發過程中,能重用的儘量重用,而且有時候使用系統提供的效果可能會更好。
其他小tips:
1. 分辨率適配-ldpi,-mdpi, -hdpi配置不同精度資源,系統會根據設備自適應,包括drawable, layout,style等不同資源。
2.儘量使用dp(density independent pixel)開發,不用px(pixel)。
3.多用wrap_content, match_parent
4.永遠不要使用AbsoluteLayout
5.使用9patch(通過~/tools/draw9patch.bat啓動應用程序),png格式
6.將Acitivity中的Window的背景圖設置爲空。getWindow().setBackgroundDrawable(null);android的默認背景是不是爲空。
7.View中設置緩存屬性.setDrawingCache爲true。
Desgin Review(設計檢查):
Desgin Review主要側重檢查一下程序的設計是否合理,包括框架的設計,界面的設計,邏輯的設計(其實這些東西開發之前就應該想好了)。
框架設計:
是否定義了自己的Activity和fragment等常用控件的基類去避免進行重複的工作
是否有完善的異常處理機制,即使真的出現OOM也不會直接崩潰導致直接退出程序
界面設計:
1.在視圖中加載你所需要的,而不是你所擁有。因爲用戶不可能同時看到所有東西。最典型的例子就是ListView中的滑動加載。
2.如果數據特別大,此時應該暗示用戶去點擊加載,而不是直接加載。
3.合理運用分屏,轉屏等,它是個雙刃劍,因爲它即可以使程序更加美觀功能更加完善,但也相應增加了資源開銷。
邏輯設計:
避免子類直接去控制父類中內容,可以使用監聽等方式去解決
關於這三點由於我工作經驗比較少,加上一時半會也想不出來多少,如果大家有建議希望可以留言,之後我給加進去。
寫在最後:
到此ANDROID內存優化上、中、下三篇全部寫完了。
內存簡介,Recoken(計算)請看:ANDROID內存優化(大彙總——上)
Reduce(減少),Reuse(重用) 請看:ANDROID內存優化(大彙總——中)
Recycle(回收), Review(檢查) 請看:ANDROID內存優化(大彙總——全)
最初寫這篇文章的原因是因爲我拿到一個國外大牛演講的PPT,我看過之後感覺寫的非常好,於是想按照ppt的思路將其總結一下。結果到寫的時候發現困難重重,因爲內存本來就是很理論的東西,很多都是靠經驗的。而我的經驗幾乎可以忽略,寫的東西完全是網上各路文章的大彙總(所以大家千萬不要叫我大神,我只是大神的搬運工。。。)
雖然如此我覺得我總結和蒐集的還算比較全面的,當然也有很多遺落也可能有很多錯誤,這個就希望大家一起幫着完善一下。
最後我把這個PPT的原件附上,裏面很多高級的東西我沒看懂(比如那個5R中其實是沒有Review的,原文是Reorder,由於這部分我看不懂而且找不到很好的資料只能自己換了一個Review),各路大神有興趣可以看看,如果可以的話寫出來分享一下。
Putting Your App on a Memory Diet, Parts I and II_Murphy
最後小嘮叨一下,我最近參加了devstore網站的一個小比賽,所以blog先停更一個月,十一之後接着寫。
在這段時間裏我正好也可以休息一下想想以後寫點什麼東西。像內存這種偏理論的東西我還是不要碰了,以後可能會多翻譯一些國外大神的文章和自己做的一些小Demo吧。
不知不覺Blog也寫了快半年了,越來越覺得Blog這種分享精神的重要性,因爲只有分享才能收穫的更多!
最後要謝謝那些關注,點贊和評論的網友們,這些真的是我能堅持下來的一個巨大動力!
參考文章:
Android內存優化http://blog.csdn.net/imain/article/details/8560986Android 內存優化http://blog.csdn.net/awangyunke/article/details/20380719
關於android性能,內存優化http://www.cnblogs.com/zyw-205520/archive/2013/02/17/2914190.htm
Java垃圾回收原理http://www.360doc.com/content/11/0911/15/18042_147476260.shtml
JVM垃圾回收(GC)原理http://www.360doc.com/content/11/0911/16/18042_147492404.shtml