Android メモリの最適化: メモリ ジッターの概念と害

メモリ スラッシングは、アプリケーションのパフォーマンスと安定性に影響を与えるメモリ管理の望ましくない現象です。この記事では、メモリ スラッシングの定義、原因、結果、検出方法を次の観点から紹介します。

1.メモリジッターの定義

メモリジッターの例の図

メモリ スラッシングとは、メモリの頻繁な割り当てとリサイクルによって引き起こされる不安定性を指します。Java では、メモリの割り当てとリサイクルはガベージ コレクター (GC) によって管理されます。GC はメモリ内のオブジェクトを定期的にスキャンして不要なオブジェクトを特定し、それらのオブジェクトが占有している領域を解放します。このプロセスはガベージ コレクション (GC) と呼ばれます。

GC は、メモリ リークを回避し、メモリ使用率を向上できる有益なメカニズムです。ただし、GC の頻度が高すぎたり、時間がかかりすぎたりすると、アプリケーションの動作効率に影響します。GC が発生すると、アプリケーション スレッドは一時停止され、実行を続行する前に GC が完了するまで待機します。このプロセスは GC 一時停止と呼ばれます。

アプリケーション内に存続期間の短いオブジェクトが多数存在する場合、またはオブジェクトのライフサイクルに一貫性がない場合、メモリの割り当てとリサイクルの数が増加し、GC の頻度と時間が増加します。これがメモリ スラッシングの性質です。

2.メモリジッターの原因

メモリ ジッターの原因は数多くありますが、一般的なシナリオをいくつか次に示します。

  • 文字列の連結: 文字列は不変オブジェクトです。文字列を連結するたびに、新しい文字列オブジェクトが作成され、古い文字列オブジェクトは破棄されます。これにより、有効期間の短い文字列オブジェクトが多数生成され、GC への負荷が増大します。例えば:
// 以下代码会创建5个字符串对象:"Hello"、"World"、"Hello World"、"!"、"Hello World!"
String s = "Hello" + "World" + "!";

  • リソースの再利用: ビットマップ、ドローアブル、ファイルなどのリソースが正しく再利用されない場合、リソースは繰り返し作成および破棄され、より多くのメモリ領域を占有し、より多くの GC が発生します。例えば:
// 以下代码每次都会创建一个新的Bitmap对象,并且在使用完毕后立即回收
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
imageView.setImageBitmap(bitmap);
bitmap.recycle();

  • 不合理なオブジェクトの作成: ループ内や頻繁に呼び出されるメソッド内で不要なオブジェクトが作成されたり、不適切なデータ構造が使用されたりすると、メモリの割り当てやリサイクルの回数が増加し、メモリ ジッターが発生します。例えば:
// 以下代码每次都会创建一个新的ArrayList对象,并且在方法返回后立即被回收
public List<String> getNames() {
    
    
    List<String> names = new ArrayList<>();
    names.add("Alice");
    names.add("Bob");
    names.add("Charlie");
    return names;
}

4.メモリジッターの影響

メモリ スラッシングは、アプリケーションに次の悪影響をもたらします。

  • 頻繁な GC : メモリの割り当てとリサイクルが頻繁すぎると、GC がより頻繁に実行され、より多くの CPU リソースが消費され、アプリケーション スレッドの実行に影響を与えます。
  • メモリ曲線がギザギザしている: メモリの割り当てとリサイクルのバランスが崩れていると、メモリ使用量が変動し、増減してジグザグの形状を形成します。これにより、OOM (メモリ不足) エラーが発生するリスクが高まります。
  • ページの遅延: GC の一時停止時間が長すぎると、アプリケーション スレッドが一時停止され、ページのレンダリングと操作に遅れが生じ、ユーザーに遅延が発生します。

5.メモリジッターの検出

アプリケーションにメモリ スラッシングがあるかどうかを検出するために、メモリ スラッシングを監視して特定できるツールがあります。例えば:

  • メモリ プロファイラ: メモリ プロファイラは、メモリ割り当て、リサイクル、リークなどを含むアプリケーションのメモリ使用量をリアルタイムで表示できる Android Studio のツールです。Memory Profiler を使用すると、メモリ曲線のギザギザの形状や GC の頻度と時間などのメモリ ジッターの現象を観察できます。
  • Allocation Tracker : Allocation Tracker は、一定期間にわたってアプリケーションによって作成されたすべてのオブジェクトと、それらのタイプ、サイズ、数量などを記録できる、Memory Profiler の機能です。Allocation Tracker を通じて、文字列の結合、リソースの再利用、不当なオブジェクトの作成など、アプリケーション内でメモリ ジッターを引き起こすコードを見つけることができます。

以下は、メモリ プロファイラと割り当てトラッカーを使用してメモリ スラッシングを検出する例の図です。

検出経路

6. 一般的なメモリ最適化方法

①. 文字列の継ぎ目を避ける:

文字列の連結は非常に非効率な操作であり、不要な文字列オブジェクトが大量に生成され、GC への負荷が増大します。文字列の連結を回避するには、次の方法を使用できます。

1.文字列ビルダー:

StringBuilder は、新しいオブジェクトを作成せずに文字列を変更したり結合したりできる可変文字列クラスです。StringBuilder を使用すると、文字列オブジェクトの作成とリサイクルを大幅に削減できます。例えば:

    // 以下代码只会创建一个StringBuilder对象和一个字符串对象:"Hello World!"
    StringBuilder sb = new StringBuilder();
    sb.append("Hello");
    sb.append("World");
    sb.append("!");
    String s = sb.toString();

2.文字列.形式:

String.format は、指定された形式文字列とパラメーターに基づいて新しい文字列オブジェクトを生成する静的メソッドです。String.format を使用すると、ループ内での文字列の結合を回避し、コードの可読性とパフォーマンスを向上させることができます。例えば:

// 以下代码只会创建一个字符串对象:"Hello World!"
String s = String.format("%s %s!", "Hello", "World");
3. リソース ファイル:

リソース ファイルはアプリケーションに保存されるテキスト ファイルで、定数や多言語文字列を保存するために使用できます。リソース ファイルを使用すると、コード内の文字列のハードコーディングを回避し、文字列オブジェクトの作成とリサイクルを減らすことができます。例えば:

    <!-- 以下代码是一个资源文件(res/values/strings.xml)中的一段内容 -->
    <resources>
        <string name="hello_world">Hello World!</string>
    </resources>

    // 以下代码只会创建一个字符串对象:"Hello World!"
    String s = getResources().getString(R.string.hello_world);

② リソースの再利用:
リソースの再利用はメモリ ジッターを最適化する効果的な方法であり、リソースの作成と破壊を削減し、メモリ使用率を向上させることができます。リソースの再利用を実現するには、次の方法を使用できます。
1.オブジェクト プール:
オブジェクト プールは、オブジェクトを毎回作成および破棄する代わりに、再利用可能なオブジェクトのセットを管理するために使用できる設計パターンです。オブジェクトが必要になった場合、オブジェクトプールからアイドルオブジェクトを取得し、使用後はオブジェクトプールに戻して次の使用を待つことができます。これにより、頻繁なメモリの割り当てとリサイクルが回避され、GC の負荷が軽減されます。例えば:

    // 以下代码是一个简单的Bitmap对象池的实现
    public class BitmapPool {
    
    
        // 一个存储Bitmap对象的队列
        private Queue<Bitmap> queue;
        // 对象池的最大容量
        private int capacity;

        // 构造方法,初始化队列和容量
        public BitmapPool(int capacity) {
    
    
            this.queue = new LinkedList<>();
            this.capacity = capacity;
        }

        // 从对象池中获取一个Bitmap对象,如果没有空闲的对象,就返回null
        public Bitmap getBitmap() {
    
    
            return queue.poll();
        }

        // 将一个Bitmap对象归还到对象池中,如果对象池已满,就回收该对象
        public void returnBitmap(Bitmap bitmap) {
    
    
            if (queue.size() < capacity) {
    
    
                queue.offer(bitmap);
            } else {
    
    
                bitmap.recycle();
            }
        }
    }

2.パラメータの再利用:
パラメータの再利用は、メソッド内で不要なオブジェクトの作成を回避する方法であり、メソッド内に新しいオブジェクトを作成する代わりに、いくつかの変数パラメータをメソッドの入出力として使用できます。これにより、オブジェクトの作成とリサイクルが減り、コードの効率が向上します。例えば:

    // 以下代码是一个计算两个向量之间夹角的方法,它使用了一个复用参数result来存储计算结果,而不是在方法内部创建一个新的float数组
    public void calculateAngle(float[] vector1, float[] vector2, float[] result) {
    
    
        // 计算两个向量的点积
        float dotProduct = vector1[0] * vector2[0] + vector1[1] * vector2[1];
        // 计算两个向量的模长
        float length1 = (float) Math.sqrt(vector1[0] * vector1[0] + vector1[1] * vector1[1]);
        float length2 = (float) Math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1]);
        // 计算两个向量之间的夹角(弧度)
        float angle = (float) Math.acos(dotProduct / (length1 * length2));
        // 将计算结果存储在复用参数result中
        result[0] = angle;
    }

③ 合理的なオブジェクト作成:
合理的なオブジェクト作成はメモリジッターを回避するための基本原則であり、コードを記述する際に不必要なオブジェクトの作成を最小限に抑えるか、より適切なデータ構造を使用する必要があります。合理的なオブジェクト作成を実現するには、次の提案に従うことができます。
1.ループまたは頻繁に呼び出されるメソッドでオブジェクトを作成しないようにします
ループまたは頻繁に呼び出されるメソッドで不要なオブジェクトが作成されると、メモリ割り当てが発生し、リサイクルが頻繁になりすぎます。 、メモリジッターの原因となります。したがって、コードを記述するときは、オブジェクトの作成をループまたはメソッドの外側に配置するか、静的変数またはメンバー変数を使用してオブジェクトを保存するようにする必要があります。例えば:

    // 以下代码是一个计算斐波那契数列第n项的方法,它使用了一个BigInteger数组来存储中间结果,但是每次调用该方法都会创建一个新的数组对象
    public BigInteger fibonacci(int n) {
    
    
        // 创建一个BigInteger数组,用来存储中间结果
        BigInteger[] array = new BigInteger[n + 1];
        // 初始化数组的第0项和第1项
        array[0] = BigInteger.ZERO;
        array[1] = BigInteger.ONE;
        // 从第2项开始,计算斐波那契数列
        for (int i = 2; i <= n; i++) {
    
    
            // 使用数组的前两项相加,得到当前项
            array[i] = array[i - 1].add(array[i - 2]);
        }
        // 返回数组的最后一项,即斐波那契数列的第n项
        return array[n];
    }

    // 以下代码是一个优化后的计算斐波那契数列第n项的方法,它使用了一个静态变量来保存BigInteger数组,避免了每次调用该方法都创建一个新的数组对象
    public class FibonacciCalculator {
    
    
        // 创建一个静态变量,用来存储BigInteger数组
        private static BigInteger[] array;

        // 计算斐波那契数列第n项的方法
        public static BigInteger fibonacci(int n) {
    
    
            // 如果静态变量为空,或者长度不足,就重新创建一个新的数组对象,并初始化第0项和第1项
            if (array == null || array.length < n + 1) {
    
    
                array = new BigInteger[n + 1];
                array[0] = BigInteger.ZERO;
                array[1] = BigInteger.ONE;
            }
            // 从第2项开始,计算斐波那契数列
            for (int i = 2; i <= n; i++) {
    
    
                // 如果当前项为null,就使用数组的前两项相加,得到当前项
                if (array[i] == null) {
    
    
                    array[i] = array[i - 1].add(array[i - 2]);
                }
            }
            // 返回数组的最后一项,即斐波那契数列的第n项
            return array[n];
        }
    }

④. 適切なデータ構造を使用する:
不適切なデータ構造を使用すると、不均衡なメモリ割り当てとリサイクルが発生したり、メモリ領域が無駄になり、メモリ ジッターが発生したりします。したがって、コードを記述するときは、実際のニーズに基づいて適切なデータ構造を選択する必要があります。例:
1.ラッパー型の代わりに基本型を使用する:
基本型 (int、float、boolean など) はスタックに直接保存され、オブジェクトを作成する必要がなく、GC もトリガーされません。ラッパー型 (Integer、Float、Boolean など) はヒープに格納されるオブジェクトであり、オブジェクトを作成して GC をトリガーする必要があります。したがって、可能な場合は、ラッパー型よりもプリミティブ型を優先して使用する必要があります。例えば:

        // 以下代码使用了包装类型Integer来存储一个整数值,这会导致内存分配和回收
        Integer value = new Integer(100);

        // 以下代码使用了基本类型int来存储一个整数值,这会避免内存分配和回收
        int value = 100;

2. HashMap の代わりに SparseArray を使用する:
SparseArray は Android で提供されるデータ構造で、キーが int 型で値が任意の型であるキーと値のペアを格納するために使用できます。SparseArray は、キーと値のペアを保持するために追加のオブジェクトを作成する必要がないため、HashMap よりもメモリ領域を節約します。したがって、可能な場合は、HashMap よりも SparseArray を使用する必要があります。例えば:

        // 以下代码使用了HashMap来存储一些键值对,其中键是int类型,值是String类型,这会导致内存分配和回收
        HashMap<Integer, String> map = new HashMap<>();
        map.put(1, "Alice");
        map.put(2, "Bob");
        map.put(3, "Charlie");

        // 以下代码使用了SparseArray来存储一些键值对,其中键是int类型,值是String类型,这会避免内存分配和回收
        SparseArray<String> array = new SparseArray<>();
        array.put(1, "Alice");
        array.put(2, "Bob");
        array.put(3, "Charlie");

5. セットの代わりに配列を使用する:
配列は、同じ型の要素のグループを格納するために使用できる固定長のデータ構造です。配列は、要素を保持するために追加のオブジェクトを作成する必要がないため、コレクション (ArrayList、LinkedList など) よりも多くのメモリ領域を節約します。したがって、可能な場合はコレクションよりも配列を優先して使用する必要があります。例えば:

    // 以下代码使用了ArrayList来存储一组整数值,这会导致内存分配和回收
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);

    // 以下代码使用了数组来存储一组整数值,这会避免内存分配和回收
    int[] array = new int[3];
    array[0] = 1;
    array[1] = 2;
    array[2] = 3;

やっと

アーキテクトになりたい場合、または 20,000 ~ 30,000 の給与範囲を突破したい場合は、コーディングとビジネスに限定されず、選択して拡張し、プログラミング的思考を向上させることができなければなりません。また、適切なキャリアプランニングも非常に重要で、学習習慣も重要ですが、最も重要なのは忍耐力であり、一貫して実行できない計画は空虚です。

方向性がわからない場合は、Alibaba の上級アーキテクトによって書かれた一連の「Android の 8 つの主要モジュールに関する上級メモ」を参照してください。これは、乱雑で散在し断片化した知識を体系的に整理して、体系的かつ効率的にマスターできるようにするのに役立ちます。 Android開発のさまざまな知識。
画像
私たちが普段読んでいる断片的な内容と比べて、このノートの知識ポイントはより体系的で理解しやすく記憶しやすく、知識体系に従って厳密に整理されています。

皆さん、ワンクリックで3回連続で応援してください。記事内の情報が必要な場合は、記事の最後にあるCSDN公式認定WeChatカードをスキャンするだけで無料で入手できます↓↓↓

PS: グループには ChatGPT ロボットもおり、全員の仕事や技術的な質問に答えることができます。

写真

おすすめ

転載: blog.csdn.net/YoungOne2333/article/details/132628268