內存泄漏是指再也不被使用的對象的內存不能被GC回收,同時頻繁的GC會形成卡頓。Android系統爲每一個應用程序分配的內存是有限的,若是應用中內存泄漏較多,就很容易形成OOMjava
Java 程序運行時的內存分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種存儲策略使用的內存空間主要分別是靜態存儲區(也稱方法區)、棧區和堆區。數據庫
• 靜態存儲區(方法區):主要存放靜態數據、全局 static數據和常量。這塊內存在程序編譯時就已經分配好,而且在程序整個運行期間都存在。緩存
• 棧區 :當方法被執行時,方法體內的局部變量都在棧上建立,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。由於棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。app
• 堆區 : 又稱動態內存分配,一般就是指在程序運行時直接 new 出來的內存。這部份內存在不使用時將會由 Java 垃圾回收器來負責回收。框架
static修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。若是你這樣作,那意味此成員變量的生命週期,會被拉長到與整個app進程生命週期一致。因此用static修飾的變量,它的生命週期是很長的,若是用它來引用一些資源耗費過多的對象,就容易出現內存泄露的狀況dom
使用static靜態變量,應注意:
第一,應該儘可能避免static成員變量引用資源耗費過多對象。
第二,在static變量引用對象的時候,在被引用對象再也不使用的時候,應及時釋放引用,作置null操做 。ide
單例模式只容許應用程序存在一個實例對象,而且這個實例對象的生命週期和應用程序的生命週期同樣長,若是單例對象中擁有另外一個對象的引用的話,這個被引用的對象就不能被及時回收。(使用弱引用)工具
若是傳入的context是Activity將會形成內存泄漏,若是是Application就不至於內存泄漏,由於Application的生命週期長。佈局
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
複製代碼
長生命週期形成的內存泄漏,如Application,由於它的生命週期和整個應用的生命週期同樣長:post
public class MyApplication extends Application {
private Activity currenActivity;
public void setCurrenActivity(Activity currenActivity){
this.currenActivity = currenActivity;
}
}
複製代碼
public class TestActivity4 extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication application = (MyApplication) getApplication();
application.setCurrenActivity(this);
}
}
複製代碼
public class TestActivity2 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.i("TestActivity2","啓動了");
}
},1000000);
}
}
複製代碼
若是啓動Activity打開後,而後當即關閉了,這種狀況下就會發生內存泄漏。咱們知道,Handler、Message、MessageQueue是相互關聯在一塊兒的,Handler經過發送消息Message與主線程進行交互,若是Handler發送的消息Message還沒有被處理,該Message及發送它的Handler對象將被MessageQueue一直持有,這樣就可能會致使Message沒法被回收。本例中Runable爲被內存泄漏的消息,又由於匿名內部類會持有外部類的引用,全部形成Activity的泄漏。不過本例中由於只會延遲一秒執行消息,因此這種內存泄漏的危害不是很大。對於Handler的使用,能夠以以下方式:
public class TestActivity2 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new NewHandler(this).sendEmptyMessageDelayed(0,1000000);
}
private static class NewHandler extends Handler{
private WeakReference<Activity> weakActivity;
NewHandler(Activity activity){
weakActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = weakActivity.get();
if(activity!=null){
Log.i("TestActivity2",activity.getClass().getSimpleName()+"啓動");
}
}
}
}
複製代碼
以靜態類的方式定義Handler,這樣就不會直接持有Activity的引用,而Activity由弱引用的方式持有,當一個對象僅僅只有弱引用,那它和沒有引用是同樣的,當GC啓動時,它將會當即被回收。
因此:Handler類須要聲明爲static,不然會發生泄漏。 緣由是Message進入消息隊列時,會持有對目標Handler的引用,若是Handler是內部類,內部類還會持有對外部類的引用, 爲了不對外部類的泄漏,Handler應該聲明爲靜態嵌套類,持有對外部類的弱引用。
對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,不然這些資源將不會被回收,形成內存泄漏。
非靜態內部類中建立了一個靜態實例,致使該實例的生命週期和應用ClassLoader級別,又由於該靜態實例又會隱式持有其外部類的引用,因此致使其外部類沒法正常釋放,出現了泄漏問題。
當您首次打開 Memory Profiler 時,您將看到一條表示應用內存使用量的詳細時間線,並可訪問用於強制執行垃圾回收、捕捉堆轉儲和記錄內存分配的各類工具。
您在 Memory Profiler(圖 2)頂部看到的數字取決於您的應用根據 Android 系統機制所提交的全部私有內存頁面數。 此計數不包含與系統或其餘應用共享的頁面。
內存計數中的類別以下所示:
• Java:從 Java 或 Kotlin 代碼分配的對象內存。
• Native:從 C 或 C++ 代碼分配的對象內存。 即便您的應用中不使用 C++,您也可能會看到此處使用的一些原生內存,由於 Android 框架使用原生內存表明您處理各類任務,如處理圖像資源和其餘圖形時,即便您編寫的代碼採用 Java 或 Kotlin 語言。
• Graphics:圖形緩衝區隊列向屏幕顯示像素(包括 GL 表面、GL 紋理等等)所使用的內存。 (請注意,這是與 CPU 共享的內存,不是 GPU 專用內存。)
• Stack: 您的應用中的原生堆棧和 Java 堆棧使用的內存。 這一般與您的應用運行多少線程有關。
• Code:您的應用用於處理代碼和資源(如 dex 字節碼、已優化或已編譯的 dex 碼、.so 庫和字體)的內存。
• Other:您的應用使用的系統不肯定如何分類的內存。
• Allocated:您的應用分配的 Java/Kotlin 對象數。 它沒有計入 C 或 C++ 中分配的對象。
當鏈接至運行 Android 7.1 及更低版本的設備時,此分配僅在 Memory Profiler 鏈接至您運行的應用時纔開始計數。 所以,您開始分析以前分配的任何對象都不會被計入。 不過,Android 8.0 附帶一個設備內置分析工具,該工具可記錄全部分配,所以,在 Android 8.0 及更高版本上,此數字始終表示您的應用中待處理的 Java 對象總數。 與之前的 Android Monitor 工具中的內存計數相比,新的 Memory Profiler 以不一樣的方式記錄您的內存,所以,您的內存使用量如今看上去可能會更高些。 Memory Profiler 監控的類別更多,這會增長總的內存使用量,但若是您僅關心 Java 堆內存,則「Java」項的數字應與之前工具中的數值類似。 然而,Java 數字可能與您在 Android Monitor 中看到的數字並不是徹底相同,這是由於應用的 Java 堆是從 Zygote 啓動的,而新數字則計入了爲它分配的全部物理內存頁面。 所以,它能夠準確反映您的應用實際使用了多少物理內存。 注:目前,Memory Profiler 還會顯示應用中的一些誤報的原生內存使用量,而這些內存其實是分析工具使用的。 對於大約 100000 個對象,最多會使報告的內存使用量增長 10MB。 在這些工具的將來版本中,這些數字將從您的數據中過濾掉。
堆轉儲顯示在您捕獲堆轉儲時您的應用中哪些對象正在使用內存。 特別是在長時間的用戶會話後,堆轉儲會顯示您認爲不該再位於內存中卻仍在內存中的對象,從而幫助識別內存泄漏。 在捕獲堆轉儲後,您能夠查看如下信息:
• 您的應用已分配哪些類型的對象,以及每一個類型分配多少。
• 每一個對象正在使用多少內存。
• 在代碼中的何處仍在引用每一個對象。
• 對象所分配到的調用堆棧。 (目前,若是您在記錄分配時捕獲堆轉儲,則只有在 Android 7.1 及更低版本中,堆轉儲才能使用調用堆棧。)
要捕獲堆轉儲,在 Memory Profiler 工具欄中點擊 Dump Java heap
在轉儲堆期間,Java 內存量可能會暫時增長。 這很正常,由於堆轉儲與您的應用發生在同一進程中,並須要一些內存來收集數據。 堆轉儲顯示在內存時間線下,顯示堆中的全部類類型,如圖 4 所示。 注:若是您須要更精確地瞭解轉儲的建立時間,能夠經過調用 dumpHprofData() 在應用代碼的關鍵點建立堆轉儲。 要檢查您的堆,請按如下步驟操做:在類列表中,您能夠查看如下信息: • Heap Count:堆中的實例數。 • Shallow Size:此堆中全部實例的總大小(以字節爲單位)。 • Retained Size:爲此類的全部實例而保留的內存總大小(以字節爲單位)。 在類列表頂部,您可使用左側下拉列表在如下堆轉儲之間進行切換: • Default heap:系統未指定堆時。 • App heap:您的應用在其中分配內存的主堆。 • Image heap:系統啓動映像,包含啓動期間預加載的類。 此處的分配保證毫不會移動或消失。 • Zygote heap:寫時複製堆,其中的應用進程是從 Android 系統中派生的。 默認狀況下,此堆中的對象列表按類名稱排列。 您可使用其餘下拉列表在如下排列方式之間進行切換: • Arrange by class:基於類名稱對全部分配進行分組。 • Arrange by package:基於軟件包名稱對全部分配進行分組。 • Arrange by callstack:將全部分配分組到其對應的調用堆棧。 此選項僅在記錄分配期間捕獲堆轉儲時纔有效。 即便如此,堆中的對象也極可能是在您開始記錄以前分配的,所以這些分配會首先顯示,且只按類名稱列出。 默認狀況下,此列表按 Retained Size 列排序。 您能夠點擊任意列標題以更改列表的排序方式。 在 Instance View 中,每一個實例都包含如下信息: • Depth:從任意 GC 根到所選實例的最短 hop 數。 • Shallow Size:此實例的大小。 • Retained Size:此實例支配的內存大小(根據 dominator 樹)。