Android | OOM について

作者:345、

序文

Android システムにはアプリごとに最大メモリ制限があり、この制限を超えると、OOM (メモリ不足) がスローされますこれは基本的に、通常はメモリが制限を超えた後にスローされる例外です。最も一般的な OOM は、メモリ リーク (大量のオブジェクトを解放できない) または必要なメモリ サイズが割り当てられたメモリ サイズより大きいことが原因で発生する OOM です。たとえば、非常に大きなイメージを読み込むと、OOM が発生する可能性があります。

一般的な OOM

ヒープオーバーフロー

ヒープ メモリのオーバーフローは最も一般的な OOM です。通常、ヒープ メモリがいっぱいでガベージ コレクタによって再利用できず、OOM が発生することが原因です。

スレッドオーバーフロー

許可される最大スレッド数は携帯電話ごとに異なります。一部の携帯電話では、この値が非常に低い値に変更されており、スレッド オーバーフローの問題が発生しやすくなります。

FD数量オーバーフロー

ファイル記述子のオーバーフローでは、プログラムが新しいファイルを開くか作成するときに、システムはプロセスによって開かれたファイルのレコード テーブルを指すインデックス値を返します。たとえば、出力ストリーム ファイルでファイルを開くとき、システムは FD を返しますが、FD が漏れる可能性があります。

仮想メモリが不十分です

新しいスレッドを作成する場合、最下層で JNIEnv オブジェクトを作成し、仮想メモリを割り当てる必要があります。仮想メモリが枯渇すると、スレッドの作成は失敗し、OOM がスローされます。

Jvm、Dvm、Art のメモリの違い

Android は Java 言語ベースの仮想マシン Dalvik/ART を使用しており、Dalvik と ART はどちらも JVM に基づいていますが、Android の仮想マシンは Android デバイス上で実行する必要があるため、標準の JVM とは異なることに注意してください。それぞれに異なる最適化と制限があります。

リサイクルに関しては、Dalvik は 1 つのリサイクル アルゴリズムのみを修正しますが、ART リサイクル アルゴリズムは実行時にオンデマンドで選択でき、ART にはメモリを整理してメモリ ホールを減らす機能があります。

JVM

JVMは、実際のコンピュータ上でさまざまなコンピュータ機能を模擬することで実現される架空のコンピュータであり、完全な(仮想的な)ハードウェアアーキテクチャとそれに対応する命令体系を持ち、その命令セットはスタック構造に基づいています。これは、Java虚拟机システム操作から独立しており、任意のシステム上で実行できるプログラムをサポートするために使用されます。

JVM によって管理されるメモリは次の部分に分割されます。

  • メソッド領域

    各スレッド ロックで共有され、仮想マシンによってロードされたクラス情報、定数、静的変数などを格納するために使用されます。メソッド領域がメモリ割り当て要件を満たさない場合、OutOfMemoryError 例外がスローされます。

    • 定数プール

      定数プールはメソッド領域の一部でもあり、コンパイラによって生成されたさまざまな引数やシンボル参照を保存するために使用されます。最も使用されるのは String です。新しい String が intern と呼ばれるとき、そのような文字列があるかどうかを確認します。定数プールにある場合はそれを返し、ない場合は作成して返します。

  • Javaヒープ

    仮想マシンのメモリ内の最大のメモリ部分。newによって作成されたすべてのオブジェクトはヒープ メモリに割り当てられます。これは仮想マシンの最大のメモリ部分であり、gc が再利用する必要がある部分でもあります。同時に、ここでも OOM が発生しやすくなります

    メモリ回復の観点から見ると、現在、ほとんどのコレクターが世代別収集方法を使用しているため、新しい世代、古い世代などに細分化することもできます。

    Java仮想マシンの規定により、Javaヒープは論理的に連続していれば物理的に不連続な空間であっても構わないため、確保できるメモリが無い場合にはOutOfMemoryError例外が発生します。

  • Javaスタック

    スレッドはプライベートであり、Java メソッドの実行時にすべてのデータを保存するために使用され、スタック フレームで構成されます。スタック フレームはメソッドの実行を表します。各メソッドの実行は、仮想マシンをスタックからスタックへ移行するプロセス。スタック フレームには主にローカル変数、スタック オペランド、ダイナミック リンクなどが含まれます。

    Java スタックは、オペランド スタック、スタック フレーム データ、およびローカル変数データに分割されます。メソッド内で割り当てられたローカル変数はスタック上にあります。同時に、各メソッド呼び出しには、スタック上のスタック フレームが伴います。スタックは両刃の剣であり、小さすぎると、特に再帰や多数のループ操作がある場合にスタック オーバーフローが発生する可能性があります。大きすぎると作成できるスタック数に影響し、マルチスレッドアプリケーションの場合はメモリオーバーフローを引き起こします。

  • ネイティブメソッドスタック

    この効果は基本的に Java スタックと似ていますが、ネイティブ メソッドを提供するために使用される点が異なります。

  • プログラムカウンター

    これは小さなスペースであり、その機能は現在のスレッド ロック実行バイトコードの行番号インジケーターと見なすことができ、スレッドによって実行されたバイトコード命令のアドレスを記録するために使用され、スレッドを元の状態に復元できます。切り替え時の正しい実行位置。

DVM

元々は Dalvik という名前でしたが、Android プラットフォーム用に Google によって設計された仮想マシンです。本質的には JAVA 仮想マシンでもあります。Android で Java プログラムを実行するための基盤です。その命令はレジスタ アーキテクチャに基づいており、その独自の命令を実行します。ファイル形式は-dex。

DVM ランタイム ヒープ

DVM のヒープ構造は JVM のヒープ構造とは異なり、主にヒープがアクティブ ヒープと Zy​​gote ヒープに分割されているという事実に反映されていますZygote は、仮想マシン プロセスおよび仮想マシン インスタンス インキュベーターです。Zygote ヒープは、Zygote プロセスが開始時にプリロードするクラス、リソース、およびオブジェクトです。さらに、コード内で作成したインスタンスと配列は、アクティブ ディレクトリに保存されますヒープ。

Dalvik ヒープを 2 つに分割する理由は主に、Android が親プロセスと子プロセスの間でのデータのコピーをできるだけ避けるために、fork メソッドによって新しい zygote プロセスを作成するためです。

Dalvik のZygote は、Android コア クラスと Java ランタイム ライブラリであるプリロードされたクラスを保存します。この部分はめったに変更されません。ほとんどの場合、子プロセスと親プロセスはこの領域を共有するため、このクラスのガベージ コレクションは必要ありません。アクティブ、プログラムコード内で作成されるインスタンスオブジェクトのヒープはガベージコレクションの重要な領域であるため、2つのヒープを分離する必要があります。

DVMリサイクルメカニズム

DVM のガベージ コレクション戦略はデフォルトでマーク アンド スイープ アルゴリズム (マーク アンド スイープ) になり、基本的なプロセスは次のとおりです。

  1. マーキング フェーズ: ルート オブジェクトからトラバースを開始し、到達可能なすべてのオブジェクトをマークし、それらを非ガベージ オブジェクトとしてマークします。
  2. クリアフェーズ: ヒープ全体を走査し、マークされていないオブジェクトをすべてクリアします。
  3. 圧縮フェーズ (オプション): インベントリ内のすべてのオブジェクトをまとめて圧縮し、メモリの断片化を軽減します。

DVM ガベージ コレクターはマーク アンド スイープ アルゴリズムに基づいていることに注意してください。このアルゴリズムはメモリ アルゴリズムを生成しますが、これによりメモリ割り当ての効率が低下する可能性があります。そのため、DVM は世代回復アルゴリズムもサポートしており、より適切に処理できます。メモリの断片化の問題。

世代別ガベージ コレクションでは、メモリが異なる年齢に分割され、各年齢が異なるガベージ コレクション アルゴリズムを使用して処理されます。若い世代はマーク コピー アルゴリズムを使用し、古い世代はマーク クリア方式を使用するため、メモリのバランスが良くなります。割り当て効率とガベージコレクション効率

美術

ART は Android 5.0 で導入された仮想マシンです。DVM と比較して、ART は AOT (Ahead of Time) コンパイル テクノロジを使用します。つまり、実行時にバイトコードを 1 つずつ解釈するのではなく、アプリケーションのバイトコードをネイティブ マシン コードに変換します。 、このコンパイル テクノロジにより、アプリケーションの実行効率が向上し、アプリケーションの起動時間とメモリ使用量が削減されます。

JITとAOTの違い
  • ジャストインタイム

    DVM は JIT コンパイラを使用し、アプリケーションが実行されるたびに dex バイトコードの一部をリアルタイムでマシンコードに変換します。プログラムの実行中に、より多くのコードがコンパイルおよびキャッシュされます。JIT はコードの一部のみを変換するため、消費メモリと占有物理メモリ領域が少なくなります。

  • 時代を先取りして

    ART には AOT コンパイラが組み込まれています。アプリケーションのインストール中に、dex バイトコードをマシン コードにコンパイルし、デバイスのメモリに保存します。このプロセスは、アプリケーションがデバイスにインストールされるときに実行されるように設計されています。JIT コンパイルは機能しないため、必要な時間が長くなり、コードの実行速度がはるかに速くなります

ART ランタイム ヒープ

DVM とは異なり、ART はさまざまなガベージ コレクション スキームを採用しており、各スキ​​ームは異なるガベージ コレクターを実行します。デフォルトでは、同時マーク削除である CMS (Concurrent Mark-Sweet) スキームを使用します。このスキームは主に Sticky-CMS を使用します。そして部分的なCMS。ART ランタイム ヒープの領域はスキームに応じて分割され、デフォルトでは 4 つの領域で構成されます。

これらは Zygote、Active、Image、Large Object で構成されます。このうち Zygote と Active の機能は DVM と同じで、Image 領域はいくつかのプリロードされたクラスを格納するために使用され、Large Object はラージ オブジェクトを割り当てるために使用されます。 (デフォルトのサイズは 12kb)、Zygote と Image はプロセス間で共有されます。

LMK メモリ管理メカニズム

LMK (Low Memory Killer) は、Android システムのメモリ管理メカニズムの一部であり、メモリが不足している場合にシステム内の不要なプロセスを解放し、システムの正常な動作を保証します。

LMK メカニズムの基本原理は、カーネル OOM メカニズムを使用してメモリを管理することです。システム メモリが不足すると、カーネルは各プロセスの優先順位に従って重要なプロセスにメモリを割り当て、同時に重要でないプロセスを終了します。システムのクラッシュを避けるため。

LKM メカニズムの使用シナリオには次のものがあります。

  • システム メモリの不足: LMK メカニズムは、システムがメモリを管理してシステムの正常な動作を保証するのに役立ちます。
  • メモリ リーク: アプリケーションでメモリ リークが発生した場合、LMK はシステムの正常な動作を保証するために、リークしたメモリを解放します。
  • プロセスの最適化: システムによるプロセス管理を支援し、システム リソースの合理的な利用を確保します。

システム メモリが不足している場合、LMK メカニズムは重要でないプロセスを終了することでメモリを解放し、システムが正常に動作するようにします。

ただし、使い方を誤るとアプリが不安定になる可能性もあります。

OOMはなぜ起こるのでしょうか?

OOM は、Android システムが仮想マシンのヒープを制限しているために表示されます。要求されたスペースがこの制限を超えると、OOM がスローされます。この目的は、システムがより多くのプロセスを同時にメモリに常駐できるようにすることです。そのため、プログラムが起動するたびにプログラムをメモリに再ロードする必要がなくなり、ユーザーの応答が速くなります。

Android は割り当てられたメモリ サイズを取得します

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.memoryClass

現在のデバイスのアプリケーションごとのおおよそのメモリ クラスを返します。これにより、システム全体を最適に動作させるためにアプリケーションにどれだけのメモリ制限を課す必要があるかがわかります。戻り値はメガバイト単位です。ベースラインの Android メモリ クラスは 16 (これは、これらのデバイスの Java ヒープ制限です) です。より多くのメモリを備えた一部のデバイスでは、24 以上の数値が返される場合があります。

私が使用している携帯電話のメモリは 16 g で、通話は 256 MB を返します。

manager.memoryClass は、build.prop の dalvik.vm.heapgrowthlimit に対応します。

より大きなヒープ メモリを適用する

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.largeMemoryClass

割り当て可能な最大メモリ制限は、マニフェスト ファイルで android:largeHeap="true" を設定して有効にする必要があります。

manager.largeMemoryClass は build.prop の dalvik.vm.heapsize に対応します

ランタイム.maxMemory

プロセスによって取得できる最大メモリ制限を取得します。これは、上記の 2 つの値のいずれかに等しいです。

/システム/ビルド.prop

このディレクトリは、Android のメモリ構成に関連するファイルであり、システム メモリ制限などのデータが保存されます。Android 構成のメモリ関連情報を確認するには、adb コマンドを実行します。

adb shell
cat /system/build.prop

デフォルトでは開くことができず、権限がなく、root が必要です

開いたら、dalvik.vm に関連する設定を見つけます。

dalvik.vm.heapstartsize=5m	#单个应用程序分配的初始内存
dalvik.vm.heapgrowthlimit=48m	#单个应用程序最大内存限制,超过将被Kill,
dalvik.vm.heapsize=256m  #所有情况下(包括设置android:largeHeap="true"的情形)的最大堆内存值,超过直接oom。

android:largeHeap="true" が設定されていない場合、要求されたメモリが heapgrowthlimit を超えている限り oom がトリガーされます。 android:largeHeap="true" が設定されている場合、メモリがヒープサイズを超えた場合にのみ oom がトリガーされます。ヒープサイズは、アプリケーションが適用できる最大メモリになっています (ネイティブによって要求されたメモリはここには含まれません)。

OOM デモ

ヒープメモリの割り当てに失敗しました

ヒープ メモリ割り当ての失敗は、/art/runtime/gc/heap.cc に対応し、次のコードになります。

oid Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) {
  // If we're in a stack overflow, do not create a new exception. It would require running the
  // constructor, which will of course still be in a stack overflow.
  if (self->IsHandlingStackOverflow()) {
    self->SetException(
        Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
    return;
  }
  //....
  std::ostringstream oss;
  size_t total_bytes_free = GetFreeMemory();
  oss << "Failed to allocate a " << byte_count << " byte allocation with " << total_bytes_free
      << " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << " until OOM,"
      << " target footprint " << target_footprint_.load(std::memory_order_relaxed)
      << ", growth limit "
      << growth_limit_;
  
  self->ThrowOutOfMemoryError(oss.str().c_str());
}

上記の分析を通じて、システムにはアプリケーションごとに最大メモリ制限があり、この値を超えると OOM が発生することもわかりました。以下のコードでこのタイプの OOM を示してみましょう。

fun testOOM() {
    val manager = requireContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    Timber.e("app maxMemory ${manager.memoryClass} Mb")
    Timber.e("large app maxMemory ${manager.largeMemoryClass} Mb")
    Timber.e("current app maxMemory ${Runtime.getRuntime().maxMemory() / 1024 / 1024} Mb")
    var count = 0
    val list = mutableListOf<ByteArray>()
    while (true) {
        Timber.e("count $count    total ${count * 20}")
        list.add(ByteArray(1024 * 1024 * 20))
        count++
    }
}

上記のコードでは、毎回 20MB を適用し、テストを 2 つのケースに分けています。

  1. LargeHeap が有効になっていません:
 E  app maxMemory 256 Mb
 E  large app maxMemory 512 Mb
 E  current app maxMemory 256 Mb
 E  count 0    total 0
 E  count 1    total 20
 E  count 2    total 40
 E  count 3    total 60
 E  count 4    total 80
 E  count 5    total 100
 E  count 6    total 120
 E  count 7    total 140
 E  count 8    total 160
 E  count 9    total 180
 E  count 10    total 200
 E  count 11    total 220
 E  count 12    total 240
java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 12386992 free bytes and 11MB until OOM, target footprint 268435456, growth limit 268435456
......
可以看到一共分配了 12次,在第十二次的时候抛出了异常,显示 分配 20 mb 失败,空闲只有 11 mb,
  1. ラージヒープを開く
app maxMemory 256 Mb                      
large app maxMemory 512 Mb
current app maxMemory 512 Mb
E  count 0    total 0
E  count 1    total 20
E  count 2    total 40
E  count 3    total 60
E  count 4    total 80
E  count 5    total 100
E  count 6    total 120
E  count 7    total 140
E  count 8    total 160
E  count 9    total 180
E  count 10    total 200
E  count 11    total 220
E  count 12    total 240
E  count 13    total 260
E  count 14    total 280
E  count 15    total 300
E  count 16    total 320
E  count 17    total 340
E  count 18    total 360
E  count 19    total 380
E  count 20    total 400
E  count 21    total 420
E  count 22    total 440
E  count 23    total 460
E  count 24    total 480
E  count 25    total 500
FATAL EXCEPTION: main
Process: com.dzl.duanzil, PID: 31874
java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 8127816 free bytes and 7937KB until OOM, target footprint 536870912, growth limit 536870912
可以看到分配了25 次,可使用的内存也增加到了 512 mb

スレッドの作成に失敗しました

スレッドの作成には大量のメモリ リソースが消費されます。作成プロセスには Java 層とネイティブ層が含まれ、次のコードに示すように、基本的にネイティブ層で完了します (/art/runtime/thread.cc に対応)

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  //........
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
  {
    std::string msg(child_jni_env_ext.get() == nullptr ?
        StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
        StringPrintf("pthread_create (%s stack) failed: %s",
                                 PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
    ScopedObjectAccess soa(env);
    soa.Self()->ThrowOutOfMemoryError(msg.c_str());
  }
}

ここでは、スレッドを作成するプロセスを確認するために、インターネットから写真を拝借しました。

上の図によると、JNI 環境の作成とスレッドの作成という 2 つの主要な部分があることがわかります。

JNI環境の作成に失敗しました
  1. FD オーバーフローにより JNIEnv の作成が失敗する
E/art: ashmem_create_region failed for 'indirect ref table': Too many open files java.lang.OutOfMemoryError:Could not allocate JNI Env at java.lang.Thread.nativeCreate(Native Method) at java.lang.Thread.start(Thread.java:730)
  1. 仮想メモリが不十分なため、JNIEnv の作成が失敗する
E OOM_TEST: create thread : 1104
W com.demo: Throwing OutOfMemoryError "Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log." (VmSize 2865432 kB)
E InputEventSender: Exception dispatching finished signal.
E MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
MessageQueue-JNI: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
E MessageQueue-JNI:      at java.lang.Thread.nativeCreate(Native Method)
E MessageQueue-JNI:      at java.lang.Thread.start(Thread.java:887)

E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: Process: com.demo, PID: 3533
E AndroidRuntime: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
E AndroidRuntime:        at java.lang.Thread.nativeCreate(Native Method)
E AndroidRuntime:        at java.lang.Thread.start(Thread.java:887)
スレッドの作成に失敗しました
  1. メモリ不足により仮想マシンが失敗する

    ネイティブは FixStackSize を通じてスレッド サイズを設定します

static size_t FixStackSize(size_t stack_size) {
  if (stack_size == 0) {
    stack_size = Runtime::Current()->GetDefaultStackSize();
  }
  stack_size += 1 * MB;
  if (kMemoryToolIsAvailable) {
    stack_size = std::max(2 * MB, stack_size);
  }  if (stack_size < PTHREAD_STACK_MIN) {
    stack_size = PTHREAD_STACK_MIN;
  }
  if (Runtime::Current()->ExplicitStackOverflowChecks()) {
    stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
  } else {
    stack_size += Thread::kStackOverflowImplicitCheckSize +
        GetStackOverflowReservedBytes(kRuntimeISA);
  }
  stack_size = RoundUp(stack_size, kPageSize);
  return stack_size;
}

W/libc: pthread_create failed: couldn't allocate 1073152-bytes mapped space: Out of memory
W/tch.crowdsourc: Throwing OutOfMemoryError with VmSize  4191668 kB "pthread_create (1040KB stack) failed: Try again"
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
        at java.lang.Thread.nativeCreate(Native Method)
        at java.lang.Thread.start(Thread.java:753)
  1. スレッド数が制限を超えています

    簡単なコードでテストしてみる

fun testOOM() {
    var count = 0
    while (true) {
        val thread = Thread(Runnable {
            Thread.sleep(1000000000)
        })
        thread.start()
        count++
        Timber.e("current thread count $count")
    }
}
通过打印日志发现,一共创建了 2473 个线程,当然这些线程都是没有任务的线程,报错信息如下所示
pthread_create failed: couldn't allocate 1085440-bytes mapped space: Out of memory
Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again" (VmSize 4192344 kB)

FATAL EXCEPTION: main
Process: com.dzl.duanzil, PID: 18085
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
	at java.lang.Thread.nativeCreate(Native Method)
通过测试可以看出来,具体的原因也是内存不足引起的,而不是线程数量超过限制,可能是测试的方法有问题,或者说是还没有达到最大线程的限制,由于手机没有权限,无法查看线程数量限制,所以等有机会了再看。

OOM監視

OOM の発生原因の多くはメモリ リークによるもので、メモリが解放できなくなり、OOM が発生することは周知のとおり、監視では主にメモリ リークの監視が行われています。メモリリーク検査の観点から、一般的に使用される監視方法をいくつか見てみましょう

リークカナリア

使い方は非常に簡単です。依存関係を追加するだけで直接使用できます。手動で初期化せずにメモリ リーク検出を実装できます。メモリ リークが発生すると、通知が自動的に送信され、クリックして特定のリークされたスタック情報。

LeakCannary は、現在のプロセスのメモリ スナップショットをダンプし、現在のプロセスを一定期間フリーズするため、デバッグ環境でのみ使用できます。そのため、公式環境での使用には適していません。

Android プロファイル

メモリ使用量を画像の形式で視覚的に表示したり、ヒープ ダンプを直接キャプチャしたり、ネイティブ メモリ (C/C++) や Java/Kotlin のメモリ割り当てをキャプチャしたりできます。オフラインでのみ使用でき、機能は非常に強力ですが、メモリリーク、ジッター、強制GCなどがあります。

リソースカナリア

ResourceCanary は Matrix のサブモジュールで、見つけにくいアクティビティ リークや繰り返し作成された冗長なビットマップを公開し、これらの問題のトラブルシューティングに役立つ参照チェーンなどの情報を提供します。

ResourceCanary は検出と分析を分離しており、クライアントはメモリ イメージ ファイルの検出とダンプのみを担当し、検査部分で生成された Hprof ファイルをトリミングしてほとんどの無駄なデータを削除します。ビットマップ オブジェクト検出も追加され、冗長なビットマップの数を減らすことでメモリ消費量の削減を促進します。

クーム

上記は両方ともオフラインでのみ使用できますが、KOOM はオンラインで使用できます KOOM は Kuaishou が開発した完全なソリューションであり、Java、ネイティブ、スレッドのリーク監視を実現できます

最適化の方向性

画像の最適化

画像が占有するメモリは画像のサイズとは関係ありません。主に画像の幅と高さ、および画像のロード方法によって決まります。たとえば、ARGB__8888 は RGB_565 が占有するメモリの 2 倍です。画像がどれだけメモリを占有しているかを計算するには、画像を最適化するいくつかの方法を次に示します。

  • 統合画像ライブラリ

    プロジェクトでは、複数の画像ライブラリを使用しないでください。画像の繰り返しキャッシュなどの一連の問題が発生します。

  • 画像圧縮

    画像リソースファイルによっては、プロジェクトに追加する際に画像を圧縮できるものがありますので、おすすめのプラグインを紹介しますCodeLocatorこのプラグインは少し前に使えなくなったようです プロジェクトに追加する際に、McImage画像を圧縮するのにおすすめのプラグインを紹介します梱包、圧縮加工。

    次のように使用します

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.smallsoho.mobcase:McImage:1.5.1'
    }
}
然后在你想要压缩的Module的build.gradle中应用这个插件,注意如果你有多个Module,请在每个Module的build.gradle文件中apply插件
apply plugin: 'McImage'
最后将我代码中的mctools文件夹放到项目根目录
mctools
配置

你可以在build.gradle中配置插件的几个属性,如果不设置,所有的属性都使用默认值
McImageConfig {
        isCheckSize true //是否检测图片大小,默认为true
        optimizeType "Compress" //优化类型,可选"ConvertWebp","Compress",转换为webp或原图压缩,默认Compress,使用ConvertWep需要min sdk >= 18.但是压缩效果更好
        maxSize 1*1024*1024 //大图片阈值,default 1MB
        enableWhenDebug false //debug下是否可用,default true
        isCheckPixels true // 是否检测大像素图片,default true
        maxWidth 1000 //default 1000 如果开启图片宽高检查,默认的最大宽度
        maxHeight 1000 //default 1000 如果开启图片宽高检查,默认的最大高度
        whiteList = [ //默认为空,如果添加,对图片不进行任何处理

        ]
        mctoolsDir "$rootDir"
        isSupportAlphaWebp false  //是否支持带有透明度的webp,default false,带有透明图的图片会进行压缩
        multiThread true  //是否开启多线程处理图片,default true
        bigImageWhiteList = [
                "launch_bg.png"
        ] //默认为空,如果添加,大图检测将跳过这些图片
}
  • 画像監視

    アプリ内のすべての写真を監視し、写真が占有しているメモリサイズをリアルタイムで確認し、写真サイズが適切かどうか、漏れがないかなどを確認し、データを取得できる分析ツールを推奨します。メモリ内の画像の数やサイズ、AndroidBitmapMonitorビットマップ作成スタックなどを取得できます。

    また、カスタマイズによりモニタリングも実現可能で、setImageDrawableなどのメソッドをモニタリングすることで、ピクチャのサイズやImageView自体のサイズを取得し、プロンプトの修正が必要かどうかを判断することができます。以下に続きます:

    1. ImageView をカスタマイズし、setImageResource、setBackground などのメソッドをオーバーライドし、その中の画像のサイズを検出します。
    2. カスタム ImageView を使用して、それらを 1 つずつ置き換えるのは明らかに非現実的です。ここでは 2 つの方法があります。1 つ目は、コンパイル中にバイトコードを変更し、すべての ImageView をカスタム ImageView に置き換えることです。2 つ目は、表示、ImageVIew をカスタム ImageView に置き換えます。これは、ワンクリックでカプセル化して置き換えることができます。
    3. チェックは非同期で、またはメインスレッドがアイドル状態のときに実行できます。
  • 積載方法の最適化

    通常の状況では、Glide または他の画像読み込みライブラリを使用して画像を読み込みます。たとえば、Glide のデフォルトの画像読み込み形式は ARGB_8888 です。この形式の場合、各ピクセルは 4 バイトを占有する必要があります。一部のローエンド コンピュータの場合は、次のように入力します。メモリ占有率を減らすために、画像の読み込み形式を RGB_565 に変更できます。ARGB_8888 と比較して、各ピクセルは 2 バイトしかないため、メモリの半分を節約でき、コストが低くなります。透明なチャネルが必要 画像の場合は、この方法でロードする方が間違いなく良い選択です。

fun loadRGB565Image(context: Context, url: String?, imageView: ImageView) {
    Glide.with(context)
        .load(url)
        .apply(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
        .into(imageView)
}
也可以指定 Bitmap 的格式
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
  • 画像のサンプリングレートを設定する
Options options = new BitmapFactory.Options();
options.inSampleSize = 5; // 原图的五分之一,设置为2则为二分之一
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                       R.id.myimage, options);
inSampleSize 值越大,图片质量越差,需谨慎设置

メモリリークの最適化

指定されたライフサイクル内にメモリが解放されない場合はメモリ リークとみなされ、メモリ リークが多すぎるとメモリの大部分を占有してしまい、新しいオブジェクトを適用できなくなります。使用可能なメモリを確認すると、OOM が表示されます。

メモリ リークを観察する方法 解決策は上で提案されていますが、ここでは主に、日常の開発プロセスでよくあるメモリ リークを回避する方法について説明します。

  1. メモリスラッシング

    メモリ ジッターは主に、一定期間内にオブジェクトが頻繁に作成されることを指します。リサイクルの速度は作成の速度ほど速くなく、多数の不連続なメモリ スライスが発生します。

    プロファイラで観察すると、次の図に示すように、GC が頻繁に発生し、メモリ曲線がギザギザになっている場合、メモリ ジッターが発生する可能性が非常に高くなります。

この状況は、オブジェクトの頻繁な作成が原因であると考えられます。たとえば、onDraw での繰り返しオブジェクトの頻繁な作成、ループ内でのローカル変数の継続的な作成などです。これらは開発時に直接回避できる問題であり、手動でキャッシュ プールを使用します。キャッシュプール内のオブジェクトを解放したり、マルチプレックスしたりします。

2. さまざまな一般的な漏洩方法

  • コレクションリーク

  • シングルトンリーク

  • 匿名の内部クラスのリーク

静的匿名内部クラスと外部クラスの関係:パラメータが渡されない場合、参照関係は存在せず、呼び出される側は外部クラスのインスタンスを必要とせず、外部クラスのメソッドと変数を呼び出すことはできません。 、独立したライフサイクルを持っています

非静的匿名内部クラスと外部クラスの間の関係は、外部クラスへの強い参照を自動的に取得します。呼び出されるときは、外部クラスのインスタンスが必要であり、それに応じて外部クラスのメソッドと変数を呼び出すことができます。外側のクラスであり、外側のクラスよりもさらに長いです。

  • 閉じられていないリソース ファイルによるリーク
    1. メイン シーケンス ブロードキャスト
    2. 入力ストリームと出力ストリームを閉じる
    3. ビットマップをリサイクルする
    4. アニメーションの破棄を停止する
    5. WebView を破棄する
    6. 時間内にログアウトする必要があるイベントバスとコンポーネントをログアウトする
  • ハンドラーによるメモリリーク
如果 Handler 中有延时任务或者等待的任务队列过长,都有可能因为 Handler 的继续执行从而导致内存泄露

解决方法:静态内部类+弱引用

```java
private static class MyHalder extends Handler {

		private WeakReference<Activity> mWeakReference;

		public MyHalder(Activity activity) {
			mWeakReference = new WeakReference<Activity>(activity);
		}

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			//...
		}
	}
	最后在Activity退出时,移除所有信息
	移除信息后,Handler 将会跟Activity生命周期同步
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		mHandler.removeCallbacksAndMessages(null);
	}
}
  • マルチスレッドはメモリリークを引き起こす

    1. 匿名の内部クラスを使用して開始されたスレッドは、デフォルトで外部オブジェクトへの参照を保持します。
    2. スレッド数が制限を超えるとリークが発生しますが、この場合はスレッドプールを使用できます。

メモリ監視と前後の戦略

オンライン/オフラインのメモリ監視を実現するには、上記のいくつかの方法を使用します。

自分でスレッドを開始して、定期的にリアルタイムのメモリ使用量を監視することもできます。メモリが緊急の場合には、Glide など、多くのメモリを消費するキャッシュをクリーンアップして解決できます。

アクティビティのボトムアップ戦略を使用し、BaseActivity の onDestory でビュー監視の削除を実行し、背景を null に設定するなどします。

パフォーマンスの最適化を包括的かつ明確に理解できるように、関連するコア ノート (基礎となるロジックに戻る) を用意しました。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/132474657