天天看點

android studio 記憶體洩漏 profiler 記憶體洩漏

android studio 記憶體洩漏 profiler 記憶體洩漏

時間線縮放控件 控制底部時間戳顯示間距。

android studio 記憶體洩漏 profiler 記憶體洩漏

當檢視某個時間段記憶體情況時,可通過這個按鈕回到實時預覽。

android studio 記憶體洩漏 profiler 記憶體洩漏

紫色的點是顯示 Activity 狀态、使用者輸入 Event 和螢幕旋轉 Event 的 Event 時間線

android studio 記憶體洩漏 profiler 記憶體洩漏

(1)**右側上半部分:**TOTOL JAVA NATIVE GRAPHICS…

Java:從 Java 或 Kotlin 代碼配置設定的對象記憶體。

Native:從 C 或 C++ 代碼配置設定的對象記憶體。

Native:即使您的應用中不使用 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 對象總數。

(2)虛線:

虛線表示配置設定的對象數,如右側的 y 軸所示。

(3)垃圾桶:

用于表示每個垃圾回收 Event 的圖示。

android studio 記憶體洩漏 profiler 記憶體洩漏

紅色圓圈時我自己畫的:

Android7.0以下版本會有,8.0沒有,這個的作用是記錄記憶體配置設定情況的。7.0以前,需要開始和結束,8.0隻要再時間線上拖動就可以了。(8.0 及更高版本附帶裝置内置分析工具,可持續跟蹤您的應用配置設定。)

注意:*重點内容*

注:在 Android 7.1 及更低版本上,您最多可以記錄 65535 個配置設定。 如果您的記錄會話超出此限值,則記錄中僅儲存最新的 65535 個配置設定。 (在 Android 8.0 及更高版本中,則沒有實際的限制。)

一、profiler能幹嘛

檢視配置設定哪些類型的對象以及它們使用多少空間。

每個配置設定的堆疊追蹤,包括在哪個線程中。

對象在何時被取消配置設定(僅當使用運作 Android 8.0 或更高版本的裝置時)。

android studio 記憶體洩漏 profiler 記憶體洩漏

點選記憶體展示下方的:arrange by package,找到我們自己的package。選中其中一個執行個體。

Arrange by class:基于類名稱對所有配置設定進行分組。

Arrange by package:基于軟體包名稱對所有配置設定進行分組。

Arrange by callstack:将所有配置設定分組到其對應的調用堆棧。

InstanceView 展示的是我們執行個體的配置設定記憶體時間,和釋放時間

CallStack 展示的是調用的堆棧,看過activity啟動源碼的同學是不是很熟悉呢。

官方文檔說明:檢視調用堆棧,7.0以下,需要手動dump。才能檢視,我用的是8.0的系統。Dump Java heap 就可以。就是我畫的那個紅圈圈左邊的圖示。

操作步驟:先force GC點選一下垃圾桶,這樣有利用于分析記憶體洩漏。

注意:如果您需要更精确地dump java heap。

import android.os.Debug;

調用Debug.dumpHprofData(“”)

或者在需要的地方:

Debug.startMethodTracing(“”);

//function…

Debug.stopMethodTracing();

都會在指定目錄生成一個hprof檔案。

分析記憶體洩漏:

Android 提供一個托管記憶體環境,當它确定您的應用不再使用某些對象時,垃圾回收器會将未使用的記憶體釋放回堆中。系統都必須在某個時間點短暫地暫停您的代碼。 大多數情況下,這些暫停難以察覺。

這是官方給出的一些記憶體方面的建議:包括如何減少apk大小,背景服務的優化

https://developer.android.com/topic/performance/memory

一些:需要注意的:

1.JobScheduler

2.nano protobufs:是一種與語言無關,平台無關的可擴充機制,由Google設計用于序列化結構化資料 - 類似于XML,但更小,更快,更簡單

3.使用優化的資料容器

4.Dagger 2:Dagger不使用反射掃描您的應用程式代碼。Dagger的靜态編譯時實作.

意味着它可以在Android應用程式中使用,而無需不必要的運作時成本或記憶體使用。

5.for建立過多對象。在ondraw中,建立paint或者bitmap。

貼出了維基百科中的工廠模式:

https://en.wikipedia.org/wiki/Factory_method_pattern

下面來說一些如何檢測記憶體洩漏:

步驟:先手動force GC 畫紅圈圈最坐标的那個圖示。點選dump java heap。

這時,studio會自動截取一定時間段的記憶體片段。可以點選儲存按鈕(下圖左邊那個綠箭頭)将這些檔案儲存下來。擴充名.hprof。然後用studio File open 打開。擴充名别打錯。

儲存的.prof檔案也可以用其他工具檢視,mat,jhat。

方法:将 HPROF 檔案從 Android 格式轉換為 Java SE HPROF 格式。 使用 android_sdk/platform-tools/ hprof-conv

hprof-conv a/b/c/heap-original.hprof h/g/iheap-converted.hprof 指定對目錄就可以了。一個輸入一個輸出。就可以用别的工具打開了。

1.我們可以通過Total Count(總執行個體數)和Heap Count(堆記憶體中執行個體數)

2.然後在下方的Reference Tree中我們可以看到目前這個執行個體對象持有的具體的對象,那麼在這一部分我們怎麼排查呢?我們主要需要關注的是 Dominating Size(目前指向的這個一條,在記憶體中占有的大小)值最大的前面幾條,為什麼呢?因為洩漏導緻記憶體無法被釋放值越大,存在洩漏的可能性越大。

3.在Analyzer中有一個功能就是 Detect Leaked Activities,點選綠色三角按鈕運作後,可以幫我們分析出目前可能存在洩漏的Activity對象。

4.Reference Tree根據裡邊的堆棧資訊一步步點開。找到有下面圖檔标記綠色箭頭的那個标記。就看确定了。

android studio 記憶體洩漏 profiler 記憶體洩漏
android studio 記憶體洩漏 profiler 記憶體洩漏
android studio 記憶體洩漏 profiler 記憶體洩漏

這就是沒被回收的執行個體,以及引用,還有調用的堆棧資訊。右鍵有選項可以調到代碼位置。

android studio 記憶體洩漏 profiler 記憶體洩漏

名詞的說明:

在類清單中,您可以檢視以下資訊:

Heap Count:堆中的執行個體數。

Shallow Size:此堆中所有執行個體的總大小(以位元組為機關)。

Retained Size:為此類的所有執行個體而保留的記憶體總大小(以位元組為機關)。

在 Instance View 中,每個執行個體都包含以下資訊:

Depth:從任意 GC 根到所選執行個體的最短 hop 數。了解為引用深度。

Shallow Size:此執行個體的大小。

Retained Size:此執行個體支配的記憶體大小(根據 dominator 樹)。

Retained Size大體可以了解為:gc之後如果不洩漏能,釋放的大小。

這是正常的activity

import android.content.Intent;
import android.os.Debug;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //try {
        //   Debug.dumpHprofData("");
        //} catch (IOException e) {
        //    e.printStackTrace();
        //}

        //Debug.startMethodTracing("");
        //Debug.stopMethodTracing();
    }

    public void btnClick(View v){
        startActivity(new Intent(this, SecondActivity.class));
    }

}
           

context被單利引用的:SecondActivity

public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Singleton.getInstance(this);

        Button button = new Button(this);
        button.setLayoutParams(new FrameLayout.LayoutParams(, ));
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        setContentView(button);

    }
}
           

官方給出的建議:

使用 Memory Profiler 時,可以跑 monkeyr unner 并嘗試強制記憶體洩漏。 在應用中引發記憶體洩漏的一種方式是,先讓其運作一段時間,然後再檢查堆。 洩漏在堆中可能逐漸彙聚到配置設定頂部。 不過,洩漏越小,您越需要運作更長時間的應用才能看到洩漏。

您還可以通過以下方式之一觸發記憶體洩漏:

将裝置從縱向旋轉為橫向,然後在不同的 Activity 狀态下反複操作多次。

旋轉裝置經常會導緻應用洩漏 Activity、Context 或 View 對象,因為系統會重新建立 Activity,而如果您的應用在其他地方保持對這些對象之一的引用,系統将無法對其進行垃圾回收。

處于不同的 Activity 狀态時,在您的應用與另一個應用之間切換(導航到主螢幕,然後傳回到您的應用)。

記憶體洩漏情況及解決辦法:

長時間引用 Activity、Context、View、Drawable 和其他對象,可能會保持對 Activity 或 Context 容器的引用。

可以保持 Activity 執行個體的非靜态内部類,如 Runnable。

對象保持時間超出所需時間的緩存。

1.static變量引起的記憶體洩漏 
因為static變量的生命周期是在類加載時開始 類解除安裝時結束,也就是說static變量是在程式程序死亡時才釋放,如果在static變量中 引用了Activity 那麼 這個Activity由于被引用,便會随static變量的生命周期一樣,一直無法被釋放,造成記憶體洩漏。

解決辦法: 
在Activity被靜态變量引用時,使用 getApplicationContext 因為Application生命周期從程式開始到結束,和static變量的一樣。

2.線程造成的記憶體洩漏 
類似于上述例子中的情況,線程執行時間很長,及時Activity跳出還會執行,因為線程或者Runnable是Acticvity内部類,是以握有Activity的執行個體(因為建立内部類必須依靠外部類),是以造成Activity無法釋放。
AsyncTask 有線程池,問題更嚴重

解決辦法: 
1.合理安排線程執行的時間,控制線程在Activity結束前結束。 
2.将内部類改為靜态内部類,并使用弱引用WeakReference來儲存Activity執行個體 因為弱引用 隻要GC發現了 就會回收它 ,是以可盡快回收

3.BitMap占用過多記憶體 
bitmap的解析需要占用記憶體,但是記憶體隻提供8M的空間給BitMap,如果圖檔過多,并且沒有及時 recycle bitmap 那麼就會造成記憶體溢出。

解決辦法: 
及時recycle 壓縮圖檔之後加載圖檔

4.資源未被及時關閉造成的記憶體洩漏 
比如一些Cursor 沒有及時close 會儲存有Activity的引用,導緻記憶體洩漏

解決辦法: 
在onDestory方法中及時 close即可

5.Handler的使用造成的記憶體洩漏 
由于在Handler的使用中,handler會發送message對象到 MessageQueue中 然後 Looper會輪詢MessageQueue 然後取出Message執行,但是如果一個Message長時間沒被取出執行,那麼由于 Message中有 Handler的引用,而 Handler 一般來說也是内部類對象,Message引用 Handler ,Handler引用 Activity 這樣 使得 Activity無法回收。

解決辦法: 
依舊使用 靜态内部類+弱引用的方式 可解決

這裡做一個擴充:

dumpsys

可以提供有關系統服務的資訊。

dumpsys -h 可以檢視支援的功能

dumpsys -l 列出能dumpsys的 service

如:

dumpsys activity -h 可以繼續檢視dumpsys activity 能用的指令。可以分析activity跳轉時,棧中的情況。舉例說明。

以上都是在adb shell 下運作。

不知道寫的好不好,都是自己學的東西。希望我們大家可以多溝通溝通有什麼介意。可以寫在下面。一起進步。