Android メモリの最適化とメモリ ジッターの実用的な解決策

問題の背景

TextView にカウンタを表示し、カウンタ値を毎秒更新する機能を持つアプリケーションがあるとします。この機能を実現するには、ハンドラーを使用して空のメッセージを送信し、メッセージを受信するとカウンターの値を更新し、再度空のメッセージを送信するというループを形成します。同時に、複雑なビジネス ロジックをシミュレートするために、ループ内に多数の配列オブジェクトを作成しました。コードは次のとおりです。

public class MainActivity extends AppCompatActivity {
    // 定义一个TextView来显示计数器的值
    private TextView textView;
    // 定义一个计数器变量
    private int counter = 0;
    // 定义一个Handler对象
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 更新计数器的值
            counter++;
            // 在TextView上显示计数器的值
            textView.setText(String.valueOf(counter));
            // 模拟一些复杂的业务逻辑,创建大量的数组对象
            for (int i = 0; i < 1000; i++) {
                int[] array = new int[1000];
                array[0] = i;
            }
            // 再次发送空消息,形成一个循环
            handler.sendEmptyMessage(0);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化TextView
        textView = findViewById(R.id.textView);
        // 发送空消息,启动循环
        handler.sendEmptyMessage(0);
    }
}

このコードには問題がないように見えますが、アプリケーションを実行すると、アプリケーションのメモリ使用量が非常に不安定で、メモリ曲線が明らかにギザギザで、GC イベントも非常に頻繁に発生していることがわかります。これはメモリ ジッターの典型的な現象です。

問題を特定する

この問題を特定するには、メモリ プロファイラを使用してアプリケーションのメモリ使用量を観察します。Memory Profiler は、メモリ割り当て、リサイクル、リークなどを含む、アプリケーションのメモリ使用量をリアルタイムで表示できる Android Studio のツールです。Memory Profiler を通じて、アプリケーションに明らかなメモリ変動と GC 頻度があることがわかります。

以下は、Memory Profiler を使用してアプリケーションのメモリ使用量を観察するグラフの例です。

図の例からわかるように、次のようになります。

  • メモリ使用量: このグラフは、さまざまな時点でのアプリケーションのメモリ使用量と、さまざまな種類のメモリ (Java ヒープ、ネイティブ ヒープ、グラフィックスなど) を示します。グラフからわかるように、アプリケーションの Java ヒープ メモリ使用量は明らかに変動しており、ジグザグの形を形成して上昇と下降を示しています。これは、アプリケーションに有効期間の短いオブジェクトが多数あり、その結果、メモリの割り当てとリサイクルが不均衡になっていることを示しています。
  • GC イベント: このグラフには、アプリケーション内でさまざまな時点で発生した GC イベントと、GC イベントの種類 (GC 割り当て、GC 同時実行など) が表示されます。グラフからわかるように、アプリケーションには非常に頻繁な GC イベントが発生しており、GC イベントはほぼ毎秒発生しています。これは、アプリケーションの GC プレッシャーが非常に高いことを示しており、GC はより多くの CPU リソースを消費し、アプリケーション スレッドの実行に影響を与える必要があります。

ここから再出力して、メモリジッターの実践的な解決策を引き続き紹介していきます。次に、メモリ プロファイラとコードのトラブルシューティングを使用して、特定のメモリ ジッター問題を次の側面から分析して解決する方法を示します。

問題分析

この問題を分析するには、メモリ プロファイラを使用してヒープ ダンプをキャプチャし、オブジェクト割り当てトレースを使用して、コードがメモリ スラッシングを引き起こしている場所と理由を特定します。ヒープ ダンプは、特定の時点でのアプリケーションのメモリ スナップショットを保存するファイルであり、アプリケーション内に存在するすべてのオブジェクトとそのタイプ、サイズ、数などを表示するために使用できます。オブジェクト割り当て追跡は、一定期間にわたってアプリケーションによって作成されたすべてのオブジェクトを、そのタイプ、サイズ、数、コール スタックなどとともに記録する機能です。ヒープ ダンプとオブジェクト割り当てのトレースを通じて、アプリケーション内のコードの場所とメモリ スラッシングの原因を発見できます。

以下は、Memory Profiler を使用してヒープ ダンプとオブジェクト割り当てトレースをキャプチャするプロットの例です。

図の例からわかるように、次のようになります。

  • ヒープ ダンプ: この機能を使用すると、特定の時点でアプリケーションのヒープ ダンプ ファイルをキャプチャし、その内容を表示できます。ヒープ ダンプ ファイルから、アプリケーション内に存在するすべてのクラスとインスタンス、およびそれらのタイプ、サイズ、数などを確認できます。さまざまな時点でのヒープ ダンプ ファイルを比較することで、どのクラスとインスタンスが寿命が短く、メモリ変動を引き起こしているかを発見できます。
  • 割り当て追跡: この機能を使用すると、アプリケーションによって一定期間にわたって作成されたすべてのオブジェクトを記録し、その内容を表示できます。オブジェクト割り当てトレースから、アプリケーションで作成されたすべてのオブジェクトと、それらのタイプ、サイズ、数量、呼び出しスタックなどを確認できます。オブジェクト割り当てトレースを分析すると、どのコードの場所で大量のオブジェクトが作成され、頻繁な GC が発生しているかを知ることができます。

これら 2 つの関数を使用すると、コードの場所とメモリ スラッシングの原因を特定できます。分析の結果、次のことがわかりました。

  • ヒープ ダンプ ファイルでは、Java ヒープ内に多数の int[] 配列オブジェクトが存在し、メモリ領域の大部分を占めていることがわかりました。また、ヒープ ダンプ ファイル内では、さまざまな時点、その数とサイズが異なります。明らかな変化があります。これは、これらの配列オブジェクトの寿命が短く、メモリの変動を引き起こしていることを示しています。
  • オブジェクト割り当ての追跡では、多数の int[] 配列オブジェクトが作成され、それらの型、サイズ、数量が一貫していることがわかりました。これらの呼び出しスタックを確認すると、それらはすべて MainActivity の handleMessage メソッドで作成されていることがわかります。これは、このメソッドが大量の配列オブジェクトを作成し、GC が頻繁に発生することを示しています。次に、メモリ プロファイラとコードのトラブルシューティングを使用して、特定のメモリ ジッター問題を次の側面から分析して解決する方法を示します。

問題が解決しました

この問題を解決するには、コード ロジックを変更してループ内で多数の配列が作成されないようにするか、オブジェクト プールを使用して配列オブジェクトを再利用し、それによってメモリの割り当てとリサイクルを削減します。以下は最適化計画です。

  • ループ内で多数の配列を作成しないようにします。ループ内で多数の配列を作成する必要がない場合は、配列の作成をループの外に配置するか、静的変数またはメンバー変数を使用して配列を保存できます。 。これにより、ループを実行するたびに新しい配列オブジェクトを作成することがなくなり、メモリの割り当てとリサイクルが削減されます。例えば:
public class MainActivity extends AppCompatActivity {
    // 定义一个TextView来显示计数器的值
    private TextView textView;
    // 定义一个计数器变量
    private int counter = 0;
    // 定义一个Handler对象
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 更新计数器的值
            counter++;
            // 在TextView上显示计数器的值
            textView.setText(String.valueOf(counter));
            // 模拟一些复杂的业务逻辑,使用一个静态变量来保存数组对象,避免在循环中创建大量数组对象
            for (int i = 0; i < 1000; i++) {
                array[0] = i;
            }
            // 再次发送空消息,形成一个循环
            handler.sendEmptyMessage(0);
        }
    };
    // 定义一个静态变量,用来保存数组对象
    private static int[] array = new int[1000];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化TextView
        textView = findViewById(R.id.textView);
        // 发送空消息,启动循环
        handler.sendEmptyMessage(0);
    }
}
  • オブジェクト プールを使用して配列オブジェクトを再利用する: ループ内で多数の配列を作成する必要がある場合、毎回配列オブジェクトを作成して破棄する代わりに、オブジェクト プールを使用して配列オブジェクトを管理し、再利用できます。配列が必要な場合は、オブジェクト プールから空き配列を取得し、使用後はオブジェクト プールに戻して次の使用を待つことができます。これにより、頻繁なメモリの割り当てとリサイクルが回避され、GC の負荷が軽減されます。例えば:
public class MainActivity extends AppCompatActivity {
    // 定义一个TextView来显示计数器的值
    private TextView textView;
    // 定义一个计数器变量
    private int counter = 0;
    // 定义一个Handler对象
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 更新计数器的值
            counter++;
            // 在TextView上显示计数器的值
            textView.setText(String.valueOf(counter));
            // 模拟一些复杂的业务逻辑,使用一个对象池来管理和复用数组对象,避免在循环中创建大量数组对象
            for (int i = 0; i < 1000; i++) {
                // 从对象池中获取一个空闲的数组对象
                int[] array = arrayPool.getArray();
                array[0] = i;
                // 将数组对象归还到对象池中
                arrayPool.returnArray(array);
            }
            // 再次发送空消息,形成一个循环
            handler.sendEmptyMessage(0);
        }
    };
    // 定义一个对象池,用来管理和复用数组对象
    private ArrayPool arrayPool;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化TextView
        textView = findViewById(R.id.textView);
        // 初始化对象池
        arrayPool = new ArrayPool(1000, 1000);
        // 发送空消息,启动循环
        handler.sendEmptyMessage(0);
    }
}

誰もがパフォーマンスの最適化をより包括的かつ明確に理解できるように、関連するコア ノート (基礎となるロジックを含む) を用意しました。https://qr18.cn/FVlo89

パフォーマンスの最適化に関する重要な注意事項:https://qr18.cn/FVlo89

起動の最適化

、メモリの最適化、

UIの最適化、

ネットワークの最適化、

ビットマップの最適化、画像圧縮の最適化マルチスレッド同時実行の最適化、データ伝送効率の最適化、ボリュームパッケージの最適化https://qr18.cn/FVlo89




「Android パフォーマンス監視フレームワーク」:https://qr18.cn/FVlo89

「Androidフレームワーク学習マニュアル」:https://qr18.cn/AQpN4J

  1. ブート初期化プロセス
  2. 起動時に Zygote プロセスを開始する
  3. 起動時に SystemServer プロセスを開始する
  4. バインダードライバー
  5. AMSの起動プロセス
  6. PMSの起動プロセス
  7. ランチャーの起動プロセス
  8. Android の 4 つの主要コンポーネント
  9. Androidシステムサービス - 入力イベント配信処理
  10. Android の基盤となるレンダリング - 画面更新メカニズムのソース コード分析
  11. Android のソースコード解析の実践

おすすめ

転載: blog.csdn.net/weixin_61845324/article/details/133245881