標籤:until 類型 block timeout avatar number location api lock
1190000006852540
主要是分三塊:
靜態儲存區:編譯時間就分配好,在程式整個運行期間都存在。它主要存放待用資料和常量。
棧區:當方法執行時,會在棧區記憶體中建立方法體內部的局部變數,方法結束後自動釋放記憶體。
堆區:通常存放 new 出來的對象。由 Java 記憶體回收行程回收。
棧與堆的區別
棧記憶體用來存放局部變數和函數參數等。它是先進後出的隊列,進出一一對應,不產生片段,運行效率穩定高。當超過變數的範圍後,該變數也就無效了,分配給它的記憶體空間也將被釋放掉,該記憶體空間可以被重新使用。
堆記憶體用於存放對象執行個體。在堆中分配的記憶體,將由Java記憶體回收行程來自動管理。在堆記憶體中頻繁的 new/delete 會造成大量記憶體片段,使程式效率降低。
對於非靜態變數的儲存位置,我們可以粗暴的認為:
四種參考型別的介紹
GC 釋放對象的根本原則是該對象不再被引用(強引用)。那麼什麼是強引用呢?
強引用(Strong Reference)
我們平常用的最多的就是強引用,如下:
IPhotos iPhotos = new IPhotos();
JVM 寧可拋出 OOM ,也不會讓 GC 回收具有強引用的對象。強引用不使用時,可以通過 obj = null 來顯式的設定該對象的所有引用為 null,這樣就可以回收該對象了。至於什麼時候回收,取決於 GC 的演算法,這裡不做深究。
軟引用(Soft Reference)
SoftReference<String> softReference = new SoftReference<>(str);
如果一個對象只具有軟引用,那麼在記憶體空間足夠時,記憶體回收行程就不會回收它;如果記憶體空間不足了,就會回收這些對象的記憶體。只要記憶體回收行程沒有回收它,該對象就可以被使用。
軟引用曾經常被用來作圖片緩衝,然而Google現在推薦用 LruCache 替代,因為 LRU 更高效。
In the past, a popular memory cache implementation was a SoftReference
or WeakReference bitmap cache, however this is not recommended.
Starting from Android 2.3 (API Level 9) the garbage collector is more
aggressive with collecting soft/weak references which makes them
fairly ineffective. In addition, prior to Android 3.0 (API Level 11),
the backing data of a bitmap was stored in native memory which is not
released in a predictable manner, potentially causing an application
to briefly exceed its memory limits and crash. 原文
大致意思是:因為在 Android 2.3 以後,GC 會很頻繁,導致釋放軟引用的頻率也很高,這樣會降低它的使用效率。並且 3.0 以前 Bitmap 是存放在 Native Memory 中,它的釋放是不受 GC 控制的,所以使用軟引用緩衝 Bitmap 可能會造成 OOM。
弱引用(Weak Reference)
WeakReference<String> weakReference = new WeakReference<>(str);
與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。因為在 GC 時,一旦發現了只具有弱引用的對象,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於記憶體回收行程是一個優先順序很低的線程,因此不一定會很快發現那些只具有弱引用的對象- -。
虛引用(PhantomReference)
顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期,也無法通過虛引用獲得對象執行個體。虛引用必須和引用隊列(ReferenceQueue)聯合使用。當記憶體回收行程準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的記憶體之前,把這個虛引用加入到與之關聯的引用隊列中。程式可以通過判斷引用隊列中是否存在該對象的虛引用,來瞭解這個對象是否將要被回收。
Android的記憶體回收機制簡介
Android 系統裡面有一個 Generational Heap Memory 模型,系統會根據記憶體中不同的記憶體資料類型分別執行不同的 GC 操作。
該模型分為三個區:
Young Generation
eden
Survivor Space
S0
S1
Old Generation
Permanent Generation
Young Generation
大多數 new 出來的對象都放到 eden 區,當 eden 區填滿時,執行 Minor GC(輕量級GC),然後存活下來的對象被轉移到 Survivor 區(有 S0,S1 兩個)。 Minor GC 也會檢查 Survivor 區的對象,並把它們轉移到另一個 Survivor 區,這樣就總會有一個 Survivor 區是空的。
Old Generation
存放長期存活下來的對象(經過多次 Minor GC 後仍然存活下來的對象) Old Generation 區滿了以後,執行 Major GC(大型 GC)。
在Android 2.2 之前,執行 GC 時,應用的線程會被暫停,2.3 開始添加了並發記憶體回收機制。
Permanent Generation
存放方法區。一般存放:
要載入的類的資訊
靜態變數
final常量
屬性、方法資訊
60 FPS
這裡簡單的介紹一下幀率的概念,以便於理解為什麼大量的 GC 容易引起卡頓。
App 開發時,一般追求介面的幀率達到60 FPS(60 幀/秒),那這個 FPS 是什麼概念呢?
Android 每隔 16 ms發出 VSYNC 訊號,觸發對 UI 的渲染(即每 16 ms繪製一幀),如果整個過程保持在 16 ms以內,那麼就會達到 60 FPS 的流暢畫面。超過了 16 ms就會造成卡頓。那麼如果在 UI 渲染時發生了大量 GC,或者 GC 耗時太長,那麼就可能導致繪製過程超過 16 ms從而造成卡頓(FPS 下降、掉幀等),而我們大腦對於掉幀的情況十分敏銳,因此如果沒有做好記憶體管理,將會給使用者帶來非常不好的體驗。
再介紹一下記憶體抖動的概念,本文後面可能會用到這個概念。
記憶體抖動
短時間內大量 new 對象,達到 Young Generation 的閾值後觸發GC,導致剛 new 出來的對象又被回收。此現象會影響幀率,造成卡頓。
記憶體抖動在 Android 提供的 Memory Monitor 中大概表現為這樣:
Android中常見的記憶體泄露及解決方案
集合類泄露
如果某個集合是全域性的變數(比如 static 修飾),集合內直接存放一些佔用大量記憶體的對象(而不是通過弱引用存放),那麼隨著集合 size 的增大,會導致記憶體佔用不斷上升,而在 Activity 等銷毀時,集合中的這些對象無法被回收,導致記憶體泄露。比如我們喜歡通過靜態 HashMap 做一些緩衝之類的事,這種情況要小心,集合內對象建議採用弱引用的方式存取,並考慮在不需要的時候手動釋放。
單例造成的記憶體泄露
單例的靜態特性導致其生命週期同應用一樣長。
有時建立單例時如果我們需要Context對象,如果傳入的是Application的Context那麼不會有問題。如果傳入的是Activity的Context對象,那麼當Activity生命週期結束時,該Activity的引用依然被單例持有,所以不會被回收,而單例的生命週期又是跟應用一樣長,所以這就造成了記憶體泄露。
解決辦法一:在建立單例的構造中不直接用傳進來的context,而是通過這個context擷取Application的Context。代碼如下:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext();// 使用Application 的context } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; }}
第二種解決方案:在構造單例時不需要傳入 context,直接在我們的 Application 中寫一個靜態方法,方法內通過 getApplicationContext 返回 context,然後在單例中直接調用這個靜態方法擷取 context。
非靜態內部類造成的記憶體泄露
在 Java 中,非靜態內部類(包括匿名內部類,比如 Handler, Runnable匿名內部類最容易導致記憶體泄露)會持有外部類對象的強引用(如 Activity),而靜態內部類則不會引用外部類對象。
非靜態內部類或匿名類因為持有外部類的引用,所以可以訪問外部類的資源屬性成員變數等;靜態內部類不行。
因為普通內部類或匿名類依賴外部類,所以必須先建立外部類,再建立普通內部類或匿名類;而靜態內部類隨時都可以在其他外部類中建立。
Handler記憶體泄露可以關注我的另一篇專門針對Handler記憶體泄露的文章:連結
WebView 的泄漏
Android 中的 WebView 存在很大的相容性問題,有些 WebView 甚至存在記憶體泄露的問題。所以通常根治這個問題的辦法是為 WebView 開啟另外一個進程,通過 AIDL 與主進程進行通訊, WebView 所在的進程可以根據業務的需要選擇合適的時機進行銷毀,從而達到記憶體的完整釋放。
AlertDialog 造成的記憶體泄露
new AlertDialog.Builder(this) .setPositiveButton("Baguette", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { MainActivity.this.makeBread(); } }).show();
DialogInterface.OnClickListener 的匿名實作類別持有了 MainActivity 的引用;
而在 AlertDialog 的實現中,OnClickListener 類將被封裝在一個 Message 對象中(具體可以看 AlertController 類的 setButton 方法),而且這個 Message 會在其內部被複製一份(AlertController 類的 mButtonHandler 中可以看到),兩份 Message 中只有一個被 recycle,另一個(OnClickListener 的成員變數引用的 Message 對象)將會泄露!
解決辦法:
Android 5.0 以上不存在此問題;
Message 對象的泄漏無法避免,但是如果僅僅是一個空的 Message 對象,將被放入對象池作為後用,是沒有問題的;
讓 DialogInterface.OnClickListener 對象不持有外部類的強引用,如用 static 類實現;
在 Activity 退出前 dismiss dialog
Drawable 引起的記憶體泄露
Android 在 4.0 以後已經解決了這個問題。這裡可以跳過。
當我們旋轉螢幕時,預設會銷毀掉當前的 Activity,然後建立一個新的 Activity 並保持之前的狀態。在這個過程中,Android 系統會重新載入程式的UI視圖和資源。假設我們有一個程式用到了一個很大的 Bitmap 映像,我們不想每次旋轉螢幕時都重新載入這個 Bitmap 對象,最簡單的辦法就是將這個 Bitmap 對象使用 static 修飾。
private static Drawable sBackground;@Overrideprotected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); if (sBackground == null) { sBackground = getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label);}
但是上面的方法在旋轉螢幕時有可能引起記憶體泄露,因為,當一個 Drawable 綁定到了 View 上,實際上這個 View 對象就會成為這個 Drawable 的一個 callback 成員變數,上面的例子中靜態 sBackground 持有 TextView 對象的引用,而 TextView 持有 Activity 的引用。當旋轉螢幕時,Activity 無法被銷毀,這樣就產生了記憶體泄露問題。
該問題主要產生在 4.0 以前,因為在 2.3.7 及以下版本 Drawable 的 setCallback 方法的實現是直接賦值,而從 4.0.1 開始,setCallback 採用了弱引用處理這個問題,避免了記憶體泄露問題。
資源未關閉造成的記憶體泄露
BroadcastReceiver,ContentObserver 之類的沒有解除註冊
Cursor,Stream 之類的沒有 close
無限迴圈的動畫在 Activity 退出前沒有停止
一些其他的該 release 的沒有 release,該 recycle 的沒有 recycle… 等等。
總結
我們不難發現,大多數問題都是 static 造成的!
在使用 static 時一定要小心,關注該 static 變數持有的引用情況。在必要情況下使用弱引用的方式來持有一些引用
在使用非靜態內部類時也要注意,畢竟它們持有外部類的引用。(使用 RxJava 的同學在 subscribe 時也要注意 unSubscribe)
注意在生命週期結束時釋放資源
使用屬性動畫時,不用的時候請停止(尤其是迴圈播放的動畫),不然會產生記憶體泄露(Activity 無法釋放)(View 動畫不會)
幾種記憶體偵查工具的介紹
Memory Monitor
Allocation Tracker
Heap Viewer
LeakCanary
Memory Monitor
位於 Android Monitor 中,該工具可以:
Allocation Tracker
該工具用途:
使用方法:在 Memory Monitor 中有個 Start Allocation Tracking 按鈕即可開始跟蹤 在點擊停止跟蹤後會顯示統計結果。
Heap Viewer
該工具用於:
顯示記憶體快照資訊
每次 GC 後收集一次資訊
尋找記憶體泄露的利器
使用方法: 在 Memory Monitor 中有個 Dump Java Heap 按鈕,點擊即可,在統計報告左上方選按 package 分類。配合 Memory Monitor 的 initiate GC(執行 GC)按鈕,可檢測記憶體泄露等情況。
LeakCanary
重要的事情說三遍:
for (int i = 0; i < 3; i++) { Log.e(TAG, "檢測記憶體泄露的神器!"); }
LeakCanary 具體使用不再贅述,自行 Google。
你可能感興趣的文章
- 記憶體 問題- 收藏集 - 掘金 137 瀏覽
- Android應用記憶體流失的定位、分析與解決方案策略 6 收藏,682 瀏覽
- 移動APP測試之android效能測試 120 瀏覽
本作品採用 知識共用署名-非商業性使用-相同方式共用 4.0 國際許可協議 進行許可 。
1 條評論
echo · 2016年09月12日
關於這裡:這裡簡單的介紹一下幀率的概念,以便於理解為什麼大量的 GC 容易引起卡頓。
我在這篇文章裡面也有對GC說明,裡面說
All the Garbage Collections are “Stop the World” events because all application threads are stopped until the operation completes.
Since Young generation keeps short-lived objects, Minor GC is very fast and the application doesn’t get affected by this.
However Major GC takes longer time because it checks all the live objects. Major GC should be minimized because it will make your application unresponsive for the garbage collection duration. So if you have a responsive application and there are a lot of Major Garbage Collection happening, you will notice timeout errors.
因為GC會停止所有線程的事件,Major GC耗費事件比較長,所以會造成應用unresponsive的現象,這更好的解釋了記憶體和渲染的關係。
請指教^_^
Android 記憶體泄露總結(附記憶體偵查工具)