Android でアプリケーションのメモリを分析する方法 (18) 最終章 - Perfetto を使用してメモリとコール スタック間のリークを表示する

Android でアプリケーションのメモリを分析する方法 (18)


前の 2 つの記事では、最初に AS を使用して Android のヒープ メモリを表示する方法を紹介し、次に MAT を使用してAndroid のヒープ メモリを表示する方法を紹介しました。AS は基本的なメモリ分析のニーズを満たすことができますが、複数のヒープの包括的な比較を行うことができないため、MAT ツールが導入されました。2 つのヒープ間の比較に適しています。これら 2 つのツールはすでにメモリの問題の 95% を解決できます。

ただし、マルチスレッドによるメモリ リークなどの極端なケースでは、上記の 2 つのツールでは問題、つまりリーク ポイントのコール スタックと呼び出しスレッドを特定できない場合があります。

Android の場合、このマルチスレッド呼び出しによって発生したメモリを特定するにはどうすればよいでしょうか? 経験から得たいくつかのヒントを次に示します。

  1. コードを追加できる場合は、さまざまなスレッドに対して、リークされたオブジェクトにスレッド ID を表すフィールドを追加します。この方法は比較的簡単なので、ここでは詳しく説明しません。
  2. コードを追加できない場合は、Java 呼び出しスタックと Java ヒープを同時に記録する必要があります。同じ期間内の論理比較に基づいて、どのコール スタックがメモリ リークの原因となったかを特定できます。たとえば、どの呼び出しスタックに最も多くの呼び出しがあり、どの呼び出しスタックに最も多くのメモリが割り当てられているかを比較します。リークしたオブジェクトと呼び出しスタックの間の関係を取得するには、時間間隔の間に差を付ける必要さえあります。これらの方法では、論理処理に一定の経験が必要になることがよくあります。

この記事では、コードを追加できない状況に焦点を当て、この極端な状況を分析します。

Android Studio では、Java コールスタックの記録と Java ヒープのダンプが提供されます。ただし、同時に使用することはできないため、タイムラインでの比較はできません。しかし、Perfetto も同様の機能を提供します。

次に、Perfetto をツールとして使用し、まず Java コールスタックと Java ヒープの同時記録を導入し、それらを論理的に比較し、リークポイントのコールスタックを取得します。その後、一定時間間隔で Java コールスタックと Java ヒープの差分比較を行い、リークポイントのコールスタックを取得します。

Perfetto はスタックとヒープダンプを同時に記録します

「Androidでアプリのメモリを解析する方法(13) - perfetto」の記事でPerfettoの使い方を紹介しました。次に、通常モードを使用して、Java ヒープと Java コールスタックを同時に記録します。

adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    size_kb: 63488
    fill_policy: DISCARD
}
buffers: {
    size_kb: 2048
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "android.packages_list"
        target_buffer: 1
    }
}
data_sources: {
    config {
        name: "android.heapprofd"
        target_buffer: 0
        heapprofd_config {
            sampling_interval_bytes: 4096
            process_cmdline: "com.example.test_malloc"
            shmem_size_bytes: 8388608
            block_client: true
            ## 只录制com.android.art的堆
            heaps: "com.android.art"
        }
    }
}
## 增加了第二个数据源
data_sources: {
    ## 数据源配置
    config {
        ## 名字必须为"android.java_hprof"
        name: "android.java_hprof"
        ## 指定目标buffer,关于目标buffer的含义见android 如何分析应用的内存(十三)
        target_buffer: 0
        ## java_hprof的配置
        java_hprof_config {
            ## dump的进程名为:"com.example.test_malloc"
            process_cmdline: "com.example.test_malloc"
        }
    }
}
## 时间修改为60s
duration_ms: 60000

EOF

上記のコマンドでは、新しい data_source を追加し、Java ヒープを記録するように指定しました。同時に、android.heapprofd という別の data_source があります。指定したプロセスのヒープメモリを記録しますが、今回はネイティブヒープは必要ないので、ヒープに「com.android.art」を設定します。

Perfetto 設定ファイルの手順については、Android がアプリケーション メモリを分析する方法 (13) - perfetto を参照してください。

結果の分析

上記のコマンドを入力してAPPを操作すると、60秒後に/data/misc/perfetto-traces/traceに結果ファイルが作成されるので、それを取り出してhttps://ui.perfetto.devで開きます。 /以下に示すように
ここに画像の説明を挿入します

写真の中の:

  • マーク 1: GC ルートへのパスの保持サイズ。
  • マーク 2: GC ルートへのこのパスのセットが保持されます。

注: オブジェクトの保持サイズは、このオブジェクトをリサイクルした後、保持サイズと同じ量のメモリがリサイクルされると理解できます。オブジェクトの Retained セットは、そのオブジェクトによって参照され、オブジェクトがリサイクルされた後にリサイクルできるオブジェクトのコレクションとして理解できます。特定のパス上の保持サイズは、このパス上のオブジェクトの保持サイズの合計です。特定のパス上の保持セットは、このパス上のオブジェクトの保持セットの合計です。

保持サイズの計算については、以下を参照してください。 < Android がアプリケーション メモリを分析する方法 (16) - AS を使用して Android ヒープを表示する>

注: 上記の操作では、意図的に小さな抜け穴を配置しました。写真内の 2 つのひし形の位置 (最初と最後に 1 つ) を注意深く見てください。リーク ポイント ダンプ ヒープとコールスタックの論理分析を実行するには、2 つのプリズムを可能な限り近づける必要があるため、上記の構成は次のように調整されます。

adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    ## 将buffer增大1000倍,否则出现Perfetto ui解析出错
    size_kb: 63488000
    fill_policy: DISCARD
}
buffers: {
    size_kb: 2048
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "android.packages_list"
        target_buffer: 1
    }
}
data_sources: {
    config {
        name: "android.heapprofd"
        target_buffer: 0
        heapprofd_config {
            sampling_interval_bytes: 4096
            process_cmdline: "com.example.test_malloc"
            shmem_size_bytes: 8388608
            heaps: "com.android.art"
            continuous_dump_config {
                ## 10s之后,才开始第一次dump
                dump_phase_ms: 10000
                ## 每隔2s,dump一次
                dump_interval_ms: 10000
            }
        }
    }
}

data_sources: {
    config {
        name: "android.java_hprof"
        target_buffer: 0
        java_hprof_config {
            process_cmdline: "com.example.test_malloc"
            continuous_dump_config {
                ## 10s后,才开始第一次dump
                dump_phase_ms: 10000
                ## 每隔2s,dump一次
                dump_interval_ms: 10000
            }
        }
    }
}
## 总时间变成 30s
duration_ms: 30000

EOF

注: ここでヒープをダンプする場合は、まず APP を起動してから、Perfetto を実行する必要があります。

得られた結果は次のとおりです。
ここに画像の説明を挿入します

以下に示すように、タイムラインを調整し、重なっている 2 つのプリズムを拡大します。
ここに画像の説明を挿入します

図の 2 番目の角柱アイコンをクリックすると、GC ルートからのフレーム グラフが表示されます。

寝耳に水!Pixel 3 の Perfetto によってプルされた Java ヒープ ダンプが参照チェーンを正しく計算していないことがわかりました。これにより、フレーム グラフが正しく反応せず、メモリ リークが発生しました。詳細な分析の結果、クラスローダーとクラスローダーがロードしたオブジェクト間の参照チェーンが正しく処理されず、その結果、GC ルートから到達可能な一部のオブジェクトが到達不能になるという問題が発生したことが判明しました。すでにオブジェクトがリークしており、リークなしになりました。

私たちの目標は、論理分析のためにコールスタックとヒープ ダンプを同時に収集することです。したがって、この影響を無視してデータベースを直接操作できます。

ヒープダンプデータベーステーブル

Java ヒープ ダンプには 3 つのテーブルのみが含まれます。

  • heap_graph_reference: ストレージ参照
  • heap_graph_object: ストレージ オブジェクト
  • heap_graph_class: ストレージ クラス

これらのテーブルの構造を視覚的に表示するため。次に、ツールを使用してトレース ファイルのデータベースをエクスポートし、データベース UI ツールを使用してそれを表示します。

データベースのエクスポート

次のコマンドを使用してデータベースをエクスポートします。

./trace_processor /Users/biaowan/Documents/trace_single_conti -e  ~/Documents/trace_to_sqlite.db

この実験では、DBeaver のコミュニティ バージョンを使用してデータベースを表示しました。以下に示すように、エクスポートされたデータベース、trace_to_sqlite.db を開きます。

ここに画像の説明を挿入します

テーブルの説明

実際に使用する前に、テーブルの各列について説明する必要があります。

heap_graph_reference

  • id この参照の一意の ID
  • type このテーブルの名前、つまり heap_graph_reference
  • Reference_set_id は、参照オブジェクト セット ID を参照します。この参照がオブジェクト内にある場合、heap_graph_object 内のreference_set_id はこの値と等しくなります。
  • owned_id は参照されるオブジェクトの ID、つまり heap_graph_object の ID です。
  • owner_id は、これによって参照されるオブジェクトの ID を使用します。
  • field_name この参照のフィールド名
  • field_type_name これによって参照されるフィールドの型名
  • deobfuscated_field_name 難読化解除後のフィールド名

ヒープグラフオブジェクト

  • id このオブジェクトのID
  • このテーブルの型名
  • アップピッド
  • graph_sample_ts サンプリング時間、つまり、このオブジェクトをダンプする時間
  • self_size セルフサイズ
  • Native_size ネイティブ サイズ
  • Reference_set_id このオブジェクトによって参照される他のオブジェクトのアプリケーション セット ID
  • Reachable ルートオブジェクトが到達可能かどうか、到達可能であればリサイクル不可、そうでなければリサイクル可能(バグあり)
  • type_id このオブジェクトに対応するクラスのID
  • root_type 空でない場合は、ルート オブジェクトであることを意味します

ヒープグラフクラス

  • id このクラスのID
  • このテーブルの型名
  • name このクラスの名前
  • deobfuscated_name 難読化解除後のこのクラスの名前
  • 場所 このクラスはどこですか?
  • classloader_id クラスローダー ID、この ID は heap_graph_object の ID です
  • superclass_id このテーブルの ID に対応する親クラス ID
  • 種類

これらのテーブルの使用手順に従って、SQL クエリ ステートメントを使用して、必要に応じてヒープ内のオブジェクトを表示できます。

次に、2 番目のヒープ ダンプでどのオブジェクトが最大のメモリ領域を占有しているかを最初に簡単に確認します。

2 番目のヒープのメモリ使用量を確認する

注: 2 番目のヒープを使用する理由は何ですか? これは、収集データでは、Perfetto の実行開始後 10 秒目から収集が開始され、10 秒後に再び収集されるためです。コールスタックとヒープの間の時間間隔が長いため、初めて収集されたデータは使用されません。

正しいタイムスタンプを選択してください

SQL ステートメントは次のように使用します。

select * from heap_graph_object group by graph_sample_ts;

ここに画像の説明を挿入します

この図から、3 つの期間があることがわかります。それらはフレーム図の 3 つのダイヤモンドに対応します。残りのダイヤモンドはコールスタックです

分析したいのは 2 番目のサンプリング時間です: 398831516584184

タイムスタンプに対応するヒープ内のオブジェクトをクラスごとに分類し、そのサイズをカウントし、逆順に出力します。

select class.id,sum(object.self_size) as totalSize,class.name 
  from heap_graph_class as class inner join heap_graph_object as object
  on class.id=object.type_id 
  where object.graph_sample_ts=398831516584184
  group by class.id
  order by totalSize desc;

結果は次のとおりです
ここに画像の説明を挿入します

図からわかるように、最大​​のオブジェクト型は int[] で、次に String が続きます。

注: このクエリには、リサイクルできるオブジェクトとリサイクルできないオブジェクトの両方が含まれます。Perfetto 自身のエラーのため、到達可能フィールドを使用してリサイクル可能かどうかを判断することはできません。実際、クラスローダーの参照関係を再帰的に処理するスクリプトを自分で作成し、データベース内の到達可能なフィールドを変更することができます。しかし、私たちの仕事は、リークされたオブジェクトとコールスタックの間の関係を見つけることです。漏洩のターゲットは実際に特定されています。つまり、前の 2 つの記事を通じて、漏洩したオブジェクトを特定できます。見る:

  • Android でアプリケーション メモリを分析する方法 (seventeen) - MAT を使用して Android ヒープを表示します: http://t.csdn.cn/c3BfM
  • Android でアプリケーション メモリを分析する方法 (16) - AS を使用して Android ヒープを表示します: http://t.csdn.cn/xYGoA

上記のクエリ結果だけであれば、int[] であるメモリ リーク オブジェクトが原因であると単純に特定することはできませんが、幸いなことに、特定を支援する AS および MAT ツールがあります。Perfetto ツールの改良 (到達可能なフィールドの値の修復) により、Perfetto のみを使用してメモリ リーク ポイントを非常にうまく見つけることもできます。

最初の 2 つの記事とこの記事はすべてテスト APP を使用しているため、メモリ リーク ポイントは int[] にあると考えられます。次のステップは、このメモリ リーク ポイントをコール スタックに接続することです。

リークしたオブジェクトをコールスタックに接続するだけです

上ではリークしたオブジェクトが int[] であると説明しましたが、コールスタック内の特定の呼び出しポイントが最も多く同時に実行される場合、またはその呼び出しポイントに割り当てられたオブジェクトが最大の場合は、単純に論理的に接続できます。 。このコールポイントが原因でメモリリークが発生したと考えられます。

以下に示すように、2 番目の山に最も近い角柱アイコンをクリックします。
ここに画像の説明を挿入します

int[] は多くのスペースを占有するため、コール スタックの合計割り当てサイズを選択します。
ここに画像の説明を挿入します

この図から、 doText() 呼び出しポイントが割り当てられたサイズのほぼ 99% を占めていることがわかります。int[] のリークがこの doText() 呼び出しポイントによって引き起こされていることは疑いの余地がありません。しかし doText() が 2 つあり、それぞれ約 60% と約 40% を占めており、これら 2 つの呼び出しポイントがメモリ リークを引き起こしていることは確かです。

次に、そのフレーム グラフを見ると、呼び出しスタック全体と呼び出しスレッドを見つけることができます。

考えてみてください。すべては単純なことのように思えますよね? 質問について考えたことはありますか?彼らのタイミングは本当に正しいでしょうか? それとも、彼らのタイミングは本当に意味があるのでしょうか?

実際、Java のヒープ ダンプのデータは、プログラムの先頭からダンプ ポイントまでのヒープ内のデータです。heapprofd 内のデータ (つまり、ここでは Java 呼び出しスタック) は、Perfetto の実行開始時から記録時点までのデータです。次のように絵を描きます
ここに画像の説明を挿入します

解決策: 読者は、アプリの起動時に計算を開始するように、アプリの起動前に Perfetto を起動することを考えるかもしれません。ただし、実際の測定によると、Java ヒープをキャプチャするには、最初にアプリを起動する必要があるため、この方法はお勧めできません。この問題を実際に解決するには、再度記録し、コールスタックとヒープでそれぞれ差分比較を実行するだけです。

差分比較を使用して、残りの特定が難しい問題を解決する

差分比較を使用すると、上記の時間の非同期開始点によって引き起こされる干渉を排除でき、また、多すぎるスレッドや多すぎるコール スタックによって引き起こされる干渉も排除できます。次に、使用手順を見てみましょう

メモリ データとコール スタックを長期間再記録する

上記の設定ファイルの合計時間を 60 秒に変更します。その後、再録音すると、下図のような状況になります。

ここに画像の説明を挿入します

上図のように6セットほどのデータをサンプリングしましたので、今度は40代と50代の2セットを選択して差分比較分析を行います。もちろん、比較のために他のグループを選択することもできます。

2 つのヒープ内で最も追加されたコンテンツを含むデータを表示する差分分析

  1. まずその間の時間を調べます。次のように
select * from heap_graph_object group by graph_sample_ts;

ここに画像の説明を挿入します

結果に基づいて、上の図の 2 つの時刻、462051768285433 と 462061780815388 を選択します。

  1. 2 つのタイム ヒープを減算し、40 秒から 50 秒で追加されたオブジェクトを残します。
    2 つのテーブルを知覚的に理解するには、次の手順を実行してそれらを表示します。
select class.id,object.graph_sample_ts,
    sum(object.self_size) as totalSize,class.name 
  from heap_graph_class as class inner join heap_graph_object as object
  on class.id=object.type_id 
  where object.graph_sample_ts=462051768285433 or 
     object.graph_sample_ts=462061780815388
  group by class.id
  order by totalSize desc;

ここに画像の説明を挿入します

図からわかるように、さまざまな期間のオブジェクトのサイズの合計です。

次に、次のように 50 秒と 40 秒の間のヒープを減算します。

select t2.totalSize - COALESCE(t1.totalSize,0) as diff,t2.name
from (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462061780815388
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t2 left join (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462051768285433
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t1 on t2.name = t1.name
order by diff desc

ここに画像の説明を挿入します

パーセンテージをより適切に計算するために、ここで合計を計算した後、次のようにそれを insert ステートメントに直接書き込みます。

select (t2.totalSize - COALESCE(t1.totalSize,0))/2119766.0 
   as percentage,t2.totalSize - COALESCE(t1.totalSize,0) as diff,t2.name
from (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462061780815388
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t2 left join (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462051768285433
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t1 on t2.name = t1.name
order by percentage desc

以下に示すように
ここに画像の説明を挿入します

この図から、オブジェクトの 97% が int[] であることがわかります。次に、40 秒と 50 秒を比較して、どのコール ポイントが最も多く呼び出されたか、またはコール ポイントが最も多くのメモリを割り当てた場合、コール ポイントの方が大きくなります。確率は int[] の 97% が生成される場所です

同じ期間内のコールスタックの差分分析

コール スタックの違いを表示する方法を紹介する前に、コール スタックに関連するデータベース内のテーブルについて知っておく必要があります。以下の 3 つのテーブルがあります。

  • heap_profile_allocation: ストレージ割り当て
  • stack_profile_frame: スタック フレーム名を格納します
  • stack_profile_callsite: 呼び出しサイトを格納します

もちろん、上記 3 つのテーブル以外にもテーブルはありますが、分析には関係ないので詳細は説明しません。すべてのテーブルの詳細については、https://perfetto を参照してください。開発/ドキュメント/分析/SQLテーブル

コールスタックテーブルの説明

ヒーププロファイルの割り当て

  • ID 一意の ID
  • このテーブル名を入力します
  • ts サンプリング時間
  • アップピッド
  • heap_name ヒープ名
  • callsite_id 呼び出しサイト ID。stack_profile_callsite の di です。
  • count は割り当ての数です。正の数はコール ポイントの割り当ての数、負の数はコール ポイントのリリースの数です。
  • size は割り当てのサイズで、これも正と負に分けられます。正の数値は割り当てサイズを示し、負の数値はリリース サイズを示します。

スタックプロファイルフレーム

  • ID 一意の ID
  • このテーブル名を入力します
  • 名前 関数名
  • マッピング この関数はどのライブラリにマッピングされます。たとえば、.dex は stack_profile_mapping の ID です。
  • rel_pc マッピング ライブラリの pc 値に対する相対値
  • symbol_set_id 関数名に対応するシンボルテーブルのID、つまりstack_profile_symbolのID
  • deobfuscated_name 難読化解除後の名前

stack_profile_callsite

  • ID 一意の ID
  • このテーブル名を入力します
  • Depth 呼び出しスタックの先頭から呼び出しスタックの先頭までの距離 関数が 1 つ多い場合、深さは 1 つ増加します。
  • parent_id この呼び出しサイトの親関数の呼び出しサイト ID。それは stack_profile_callsite の ID です
  • Frame_id フレーム ID、つまり stack_profile_frame の ID

各サンプリング時間を表示する

表示するには次のコマンドを使用します。

select *,sum(count) as totalCount ,sum (size) as totalSize 
from heap_profile_allocation group by ts;

ここに画像の説明を挿入します

図からわかるように、データ全体は 6 つの期間に分割されており、フレーム チャートの 6 つのひし形に対応しています。フレーム グラフでは、各プリズムは、グラブの開始からプリズム位置に対応する時間までのすべての割り当てを表します。

ただし、データベース内の各時点は、前回の時点から今回までに取得されたすべてのデータを表すため、40 年代から 50 年代のデータを表示するには、50 年代のデータのみを確認する必要があります。それは最後から 2 行目です。

40代、50代の通話内容を確認する

理解を容易にするために、次のステートメントでは、40 代と 50 代の両方のヒープをリストして分析します。次のように

select * 
from (
   select * 
   from heap_profile_allocation 
   where ts = 462061299971174 
   order by count desc
) as t1
union all 
select * 
from (
   select * 
   from heap_profile_allocation 
   where ts = 462071449972185 
   order by count desc
) as t2

ここに画像の説明を挿入します

上の図は、30 ~ 40 間隔の状況と 40 ~ 50 間隔の状況を示しています。各コール ポイントのメモリ割り当てを少し計算すると、701 コール ポイントのメモリ割り当ては 82 を占めることがわかります。 % したがって、40 秒と 50 秒の間のメモリ リークは 701 割り当てポイントが原因であると大胆に結論付けることができます。

701 割り当てポイントのコール スタックを表示する

701 コール スタック全体を表示するには、次の再帰ステートメントを使用します。

WITH RECURSIVE RecursiveCTE AS (
    SELECT id, parent_id,frame_id
    FROM stack_profile_callsite
    WHERE id = 701
    UNION ALL
    SELECT origin.id, origin.parent_id,origin.frame_id
    FROM stack_profile_callsite origin
    JOIN RecursiveCTE r ON r.parent_id = origin.id
)
SELECT result.id,result.parent_id,frame.name 
FROM RecursiveCTE as result inner join stack_profile_frame as frame 
   on frame.id=result.frame_id 
order by result.id desc;

以下に示すように
ここに画像の説明を挿入します

直接観察すると、40 秒から 50 秒の間でリークされたオブジェクト int[] が、上図のコール スタックによってリークされていることがわかります。

同じ分析方法を使用してコール ポイント 518 を確認すると、同じ結論に達することになりますが、これらは 30 代から 40 代の間に発生していることに注意してください。同じ手順、これ以上の例はありません

この時点で、メモリ分析に perfetto を使用することが導入されました。

記憶方法のまとめ

Android のメモリ解析が導入されました。それでは、これまでの記事をすべて要約してみましょう。

ネイティブ記事

  1. 0 番目のツール xdd: メモリのみを表示できます
  2. 最初のツールは gdb です。これは、任意の場所のレジスタとメモリを表示し、コアダンプを分析し、スタック状態を表示できますが、ヒープ状態は表示できません。
  3. 2 番目のツール lldb: 表示できます。任意の場所のレジスタとメモリを表示し、コアダンプを分析し、スタックは表示できますが、ヒープは表示できません。
  4. 3 番目のツールは malloc をカスタマイズします。これはヒープの状況のみを表示できます。また、表示の範囲は狭く、ほとんど自分でコンパイルしたコードのみです。
  5. 4 番目のツール、malloc フック: すべてのヒープ割り当てを表示できます。
  6. 5 番目のツール、malloc 統計と libmemunreachable: すべてのヒープ割り当てを表示できます
  7. 6 番目のツール、malloc デバッグと libc コールバック: すべてのヒープ割り当てを表示できます
  8. 7 番目のツール ASan/HWASan: Linux のヒープ割り当てを表示することのみができますが、Android の割り当てを見つけることはできません。知識を完全にするためにここにリストされています。
  9. 8 番目のツール perfetto: ヒープ メモリの割り当て状況の表示のみが可能

Javaの記事

  1. 0 番目のツール jdb: ヒープ フレーム、ローカル変数、ロック、オブジェクトを表示する
  2. vscode 用の最初のツール Java デバッガー: スタック、ローカル変数、オブジェクトの表示
  3. 2 番目のツール Android Studio: ビュー ヒープ、オブジェクト参照、保持サイズ、コール スタック
  4. 3 番目のツール MAT: ヒープ、オブジェクト参照、保持サイズを表示し、ヒープ間の差分分析を実行します。
  5. 4 番目のツール Perfetto: ヒープ、オブジェクト参照、保持サイズ、コール スタックを表示し、ヒープとコール スタックの間の差分分析を実行します。

このシリーズの終わり。

おすすめ

転載: blog.csdn.net/xiaowanbiao123/article/details/132248383