前语

Android 体系对每个app都会有一个最大的内存束缚,假如超出这个束缚,就会抛出 OOM,也便是Out Of Memory 。本质上是抛出的一个反常,一般是在内存超出束缚之后抛出的。最为常见的 OOM 便是内存走漏(许多的目标无法被开释)导致的 OOM,或许说是需求的内存巨细大于可分配的内存巨细,例如加载一张十分大的图片,就可能呈现 OOM。

常见的 OOM

堆溢出

堆内存溢出是最为常见的 OOM ,通常是因为堆内存现已满了,而且不能够被废物收回器收回,然后导致 OOM。

线程溢出

不同的手机允许的最大线程数量是不相同的,在有些手机上这个值被修正的十分低,就会比较容易呈现线程溢出的问题

FD数量溢出

文件描述符溢出,当程序翻开或许新建一个文件的时分,体系会回来一个索引值,指向该进程翻开文件的记载表,例如当咱们用输出流文件翻开文件的时分,体系就会回来咱们一个FD,FD是可能呈现走漏的,例如输入输出流没有封闭的时分,详细可参阅 Android FD走漏问题

虚拟内存不足

在新建线程的时分,底层需求创立 JNIEnv 目标,而且分配虚拟内存,假如虚拟内存耗尽,会导致创立线程失利,并抛出 OOM。

Jvm,Dvm,Art的内存差异

Android 中运用的是依据 Java 语言的虚拟机 Dalvik / ART ,而 Dalvik 和 ART 都是依据 JVM 的,可是需求留意的是 Android 中的 虚拟器和规范的 JVM 有所不同,因为它们需求运转在 Android 设备上,因而他们具有不同的优化和束缚。

在收回方面,Dalvik 仅固定一种收回算法,而 ART 收回算法可在运转期按需挑选,而且ART 具有内存整理能力,削减内存空泛。

JVM

JVM 是一个虚构出来的核算机,是经过在实际核算机上仿真各种核算机功用来完结的,它有完善的(虚拟)硬件架构,还有相应的指令体系,其指令集依据仓库结构。运用 Java虚拟机便是为了支撑体系操作无关,在任何体系中都能够运转的程序。

JVM 将所管理的内存分为以下几个部分:

Android | 关于 OOM 的那些事

  • 办法区

    各个线程锁共享的,用于存储现已被虚拟机加载的类信息,常量,静态变量等,当办法区无法满意内存分配需求时,将会抛出 OutOfMemoryError 反常。

    • 常量池

      常量池也是办法区的一部分,用于寄存编译器生成的各种自变量和符号引证,用的最多的便是 String,当 new String 并调用intern 时,就会在常量池查看是否有该字符串,有则回来,没有则创立一个并回来。

  • Java 堆

    虚拟机内存中最大的一块内存,一切经过 new 创立的目标都会在堆内存进行分配,是虚拟机中最大的一块内存,也是gc需求收回的部分,一起OOM也容易产生在这儿

    从内存收回角度来看,因为现在搜集器大都选用分代搜集法,所以还能够细分为新生代,老时代等。

    依据 Java 虚拟机规定,Java 堆能够处于物理上不接连的空间,只需逻辑上是接连的就行,假如对中没有可分配内存时,就会呈现 OutOfMemoryError 反常

  • Java 栈

    线程私有,用来寄存 java 办法履行时的一切数据,由栈贞组成,一个栈贞就代表一个办法的履行,每个办法的履行就适当于是一个栈贞在虚拟机中从入栈到出栈的进程。栈贞中首要包含,局部变量,栈操作数,动态链接等。

    Java 栈区分为操作数栈,栈帧数据和局部变量数据,办法中分配的局部变量在栈中,一起每一次办法的调用都会在栈中奉陪栈帧,栈的巨细是把双刃剑,分配太小可能导致栈溢出,特别是在有递归,许多的循环操作的时分。假如太大就会影响到可创立栈的数量,假如是多线程运用,就会导致内存溢出。

  • 本地办法栈

    与 java 栈的效果基本类似,差异只不过是用来服务于 native 办法。

  • 程序计数器

    是一块较小的空间,它的效果能够看做是当时线程锁履行字节码的行号指示器,用于记载线程履行的字节码指令地址,使得线程切换时能够恢复到正确的履行方位。

DVM

原名 Dalvik 是 Google 公司自己规划用于 Android 渠道的虚拟机,本质上也是一个 JAVA 虚拟机,是 Android 中 Java 程序运转的根底,其指令依据寄存器架构,履行其特有的文件格局-dex。

DVM 运转时堆

DVM 的堆结构和 JVM 的堆结构有所差异,首要体现在将堆分成了 Active 堆 和 Zygote 堆。Zygote 是一个虚拟机进程,一起也是一个虚拟机实例孵化器,zygote 堆是 Zygote 进程在发动时预加载的类,资源和目标,除此之外咱们在代码中创立的实例,数组等都是存储在 Active 堆中的。

为什么要将 Dalvik 堆分为两块,首要是因为 Android 经过 fork 办法创立一个新的 zygote 进程,为了尽量防止父进程和子进程之间的数据拷贝。

Dalvik 的 Zygote 对寄存的预加载类都是 Android 核心类和 Java 运转时库,这部分很少被修正,大多数状况下子进程和父进程共享这块区域,因而这部分类没有必要进行废物收回,而 Active 作为程序代码中创立的实例目标的堆,是废物收回的要点区域,因而需求将两个堆分开。

DVM 收回机制

DVM 的废物收回战略默许是符号铲除算法(mark-and-sweep),基本流程如下

  1. 符号阶段:从根目标开始遍历,符号一切可达目标,将它们符号为非废物目标
  2. 清楚阶段:遍历整个堆,将一切未被符号的目标铲除
  3. 紧缩阶段(可选):将一切存货的目标紧缩到一起,以便削减内存碎片

需求留意的是 DVM 废物收回器是依据符号铲除算法的,这种算法会产生内存算法,可能会导致内存分配功率下降,因而 DVM 还支撑分代收回算法,能够更好的处理内存碎片问题。

在分代废物收回中,内存被分为不同的时代,每个时代运用不同的废物收回算法进行处理,年青代运用符号仿制算法,老时代运用符号铲除法,这样能够更好的平衡内存分配功率和废物收回功率

ART

ART 是在 Android 5.0 中引入的虚拟机,与 DVM 比较,ART 运用的是 AOT(Ahead of Time) 编译技能,这意味着他将运用程序的字节码转换为本机机器码,而不是在运转时逐条解说字节码,这种编译技能能够进步运用程序的履行功率,削减运用程序发动时间和内存占用量

JIT 和 AOT 差异
  • Just In Time

    DVM 运用 JIT 编译器,每次运用运转时,它实时的将一部分 dex 字节码翻译成机器码。在程序的履行进程中,更多的代码被编译缓存,因为 JIT 只翻译一部分代码,它耗费更少的内存,占用更少的物理内存空间

  • Ahead Of Time

    ART 内置了一个 AOT 编译器,在运用装置期间,她将 dex 字节码编译成机器码存储在设备的存储器上,这个进程旨在运用装置到设备的时分产生,因为不在需求 JIT 编译,代码的履行速度回快许多

ART运转时堆

与 DVM 不同的是,ART 选用了多种废物搜集计划,每个计划会运转不同的废物搜集器,默许是选用了 CMS (Concurrent Mark-Sweep) 计划,也便是并发符号铲除,该计划首要运用了 sticky-CMS 和 partial-CMS。依据不同的计划,ART 运转时堆的空间也会有不同的区分,默许是由四个区域组成的。

分别是 Zygote、Active、Image 和 Large Object 组成的,其中 Zygote 和 Active 的效果越 DVM 中的效果是相同的,Image 区域用来寄存一些预加载的类,Large Object 用来分配一下大目标(默许巨细为12kb),其中 Zygote 和 Image 是进程间共享的,

LMK 内存管理机制

LMK(Low Memory Killer) 是 Android 体系内存管理机制的一部分,LMK 是在内存不足时开释体系中不必要的进程,以保证体系的正常运转。

LMK 机制的底层原理是运用内核 OOM 机制来管理内存,当体系内存不足时,内核会依据各个进程优先级将内存优先分配给重要的进程,一起会完毕一下不重要的进程,防止体系崩溃。

LKM 机制的运用场景包含:

  • 体系内存不足:LMK 机制协助体系管理内存,以保证体系正常运转
  • 内存走漏:当运用存在内存走漏时,LMK 会将走漏的内存开释掉,以保证体系正常运转
  • 进程优化:协助体系管理进程,以保证体系资源的合理运用

在体系内存严重的状况下,LMK 机制能够经过完毕不重要的进程来开释内存,以保证体系正常运转。

可是假如不当运用,它也可能导致运用程序的不稳定。

为什么会呈现 OOM?

呈现 OOM 是应为 Android 体系对虚拟机的 heap 做了束缚,当请求的空间超越这个束缚时,就会抛出 OOM,这样做的目的是为了让体系能一起让比较多的进程常驻于内存,这样程序发动时就不必每次都从头加载到内存,能够给用户更快的呼应

Android 获取可分配的内存巨细

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

回来当时设备的近似每个运用程序内存类。这让你知道你应该对运用程序施加多大的内存束缚,让整个体系工作得最好。回来值以兆字节为单位; 基线Android内存类为16 (恰好是这些设备的Java堆束缚); 一些内存更多的设备可能会回来24乃至更高的数字。

我运用的手机内存是 16 g,调用回来的是 256Mb,

manager.memoryClass 对应 build.prop 中 dalvik.vm.heapgrowthlimit

请求更大的堆内存

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

可分配的最大对内存上限,需求在 manifest 文件中设置 android:largeHeap=”true” 方可启用

manager.largeMemoryClass 对应 build.prop 中 dalvik.vm.heapsize

Runtime.maxMemory

获取进程最大可获取的内存上限,等于上面这两个值之一

/system/build.prop

该目录是Android内存装备相关的文件,里边保存了体系的内存的束缚等数据,履行 adb 指令可看到 Android 装备的内存相关信息:

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”的时分,只要内存超越了heapsize才会触发oom。heapsize现已是该运用能请求的最大内存(这儿不包含native请求的内存)。

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,测验分为两种状况,

  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,

  2. 开启 largeHeap

    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 层 和 native 层,本质上是在 native 层完结的,对应的是 /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());
  }
}

这儿借用网上的一张相片来看一下创立线程的流程

Android | 关于 OOM 的那些事

依据上图能够看到首要有两部分,分别是创立 JNI Env 和 创立线程

创立 JNI Env 失利
  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)
    
  2. 虚拟内存不足导致 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. 虚拟机内存不足导致失利

    native 经过 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)
    
  2. 线程数量超越束缚

    用一段简略的代码来测验一下

    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,所以监控首要监控的是内存走漏,现在市面上关于内存走漏查看这方面现已十分成熟了,咱们来看几个常用的监控办法

LeakCanary

运用十分简略,只需求增加依靠后就能够直接运用,无需手动初始化,就能完结内存走漏检测,当内存产生走漏后,会主动宣布一个通知,点击就能够查看详细的走漏仓库信息

LeakCannary 只能在 debug 环境运用,因为他是在当时进程 dump 内存快照,会冻住当时进程一段时间,所以不适于在正式环境运用。

Android Profile

能够以图画的办法直观的查看内存运用状况,而且能够直接 capture heap dump,或许抓取原生内存(C/C++) 以及 Java/Kotlin 内存分配。只能在线下运用,功用十分强壮,可是吧内存走漏,抖动,强制 GC 等。

ResourceCanary

ResourceCanary 属于 Matrix 的一个子模块,它将本来难以发现的 Acivity 走漏和 Activity 走漏和重复创立的沉余的 Bitmap 暴露出来,并供给引证链等信息协助排查这些问题

ResourceCanary 将检测和剖析分离,客户端只负责检测和dump内存镜像文件,而且对查看部分生成的 Hprof 文件进行了裁剪,移除了大部分无用数据。也增加了 Bitmap 目标检测,方便经过削减沉余 Bitmap 数量,下降内存耗费。

运用可查看 Matrix

KOOM

上面的两者都只能在线下运用,而 KOOM 能够再线上运用,KOOM 是快手出的一套完整的处理计划,能够完结 Java,native 和 thread 的走漏监控

运用可查看 KOOM

优化方向

图片优化

图片所占用的内存其实和图片的巨细是没有太大联系的,首要取决于图片的宽高以及图片的加载办法,例如 ARGB__8888 就比 RGB_565 所占用的内存大了一倍,要核算图片占用了多大内存,可查看这篇文章 核算图片占用内存巨细,下面介绍几种图片的优化办法

  • 一致图片库

    在项目中,应该防止运用多种图片库,多种图片库会导致图片的重复缓存等一系列的问题

  • 图片紧缩

    关于一些图片资源文件,能够再增加到项目中的时分对图片进行紧缩处理,这儿引荐一个插件 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"
            ] //默许为空,假如增加,大图检测将跳过这些图片
    }
    
  • 图片监控

    经过对 app 中一切的图片监控,实时查看图片占用的内存巨细,然后发现图片巨细是否适宜,是否存在走漏等,引荐一个剖析东西 AndroidBitmapMonitor,该东西可获取内存中图片的数量及占用巨细,而且能够获取 Bitmap 的创立仓库等。

    另外,也能够经过自定义完结监控,经过监听 setImageDrawable 等办法,获取图片的巨细以及 ImageView 自身的巨细,然后再判别提示是否需求修正即可,详细计划如下:

    1. 自定义 ImageView,重写 setImageResource ,setBackground 等办法,在里边检测图片的巨细
    2. 运用自定义的 ImageView,假如挨个替换必定不现实,这儿供给两种办法,第一种,在编译期修正字节码,将一切的 ImageView 都替换为自定义的 ImageView,第二种,重写 Layoutinflat.Fractory2,在创立 View 的时分将 ImageVIew 替换为自定义的 ImageView,可封装一下,一键式替换。
    3. 查看的时分能够异步检测,或许在主线程闲暇的时分查看。
  • 优化加载办法

    一般状况下,咱们加载图片都运用的是 Glide 或许别的图片加载库,就比如 Glide 默许的图片加载格局是 ARGB_8888,关于这种格局,每个像素需求占用四个字节,关于一些低端机型,为了下降内存的占有率,能够修正图片的加载格局为 RGB_565 ,比较于 ARGB_8888 每个像素只要两个字节,故能够节省一半的内存,代价便是少了通明通道,关于不需求通明通道的图片来说,运用这种办法加载无疑是更好的挑选。

    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. 内存抖动

    内存抖动首要说的是在一段时间内频频的创立目标,而收回的速度更不上创立的速度,而且导致了许多不接连的内存片。

    经过 Profiler 观察,假如频频的呈现 GC ,内存曲线呈锯齿状,就极有可能产生内存抖动,如下图所示:

​呈现这种状况必定便是频频的创立目标而导致的,例如:在 onDraw 中频频的创立重复目标,在循环中不断创立局部变量等等,这些都是能够在开发中直接防止的问题,以及运用缓存池,手动开释缓存池中的目标,多进行复用等等。

Android | 关于 OOM 的那些事
2. 各种常见的走漏办法

  • 集合走漏

  • 单例走漏

  • 匿名内部类走漏

     静态匿名内部类 和外部类的联系:**假如没有传入参数就没有引证联系,被调用是不需求外部类的实例**,不能调用外部类的办法和变量,具有自主的生命周期
     非静态匿名内部类 和外部类的联系,**主动获取外部类的强引证,被调用时需求外部类实例**,能够调用外部类的办法和变量,依靠于外部类,乃至比外部类更长。
    
    • 资源文件未封闭形成的走漏

      1. 主序播送
      2. 封闭输入输出流
      3. 收回 Bitmap
      4. 停止销毁动画
      5. 销毁 WebView
      6. 及时刊出 eventBus 以及需求刊出的组件等
  • Handler 形成的存走漏

     假如 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 等一些占用内存较大的缓存来救急

运用 Activity 兜底战略,在 BaseActivity 的 onDestory 做一些 View 的监听移除,背景置 null 等等。

终究

本文到此完毕,文章中的内容也是经过积累和查看阅览资料找到的,当然本文所讲的优化办法也是最根底最简略的,假如有任何问题可直接留言或许私信。

都看到这了,就动动发财的小手点个赞呗!

参阅链接

【功能优化】大厂OOM优化和监控计划

深入探求 Android 内存优化上

深入探求 Android 内存优化下

DVM和ART原理初探

Android OOM 问题探求

内存优化 根底论 初识Android内存优化

….