一、APP 内存限制
Android 给每个 App 分配一个 VM,让 App 运行在 dalvik 上,这样即使 App 崩溃也不会影响到系统。系统给 VM 分配了一定的内存大小,App 可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余,如果应用超出 VM 最大内存,就会出现内存溢出 crash。
由程序控制操作的内存空间在 heap 上,分 java heapsize 和 native heapsize。
Java 申请的内存在 vm heap 上,所以如果 java 申请的内存大小超过 VM 的逻辑内存限制,就会出现内存溢出的异常。(如:-Xmx4096)
native 层内存申请不受其限制,native 层受 native process 对内存大小的限制。
Android 虚拟机申请内存最大内存是有限制的,不同设备申请的最大内存是不一样的。
二、内存的三大问题
- 内存抖动:内存波动图形呈锯齿状,GC 导致卡顿。
- 内存泄漏:在当前应用周期内不再使用的对象被 GC Roots 引用,导致不能回收,使实际可使用内存变小。
- 内存溢出:即 OOM,OOM 时会导致程序异常。Android 设备出厂以后,虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会 OOM。
2.1、内存抖动(Memory Churn)
内存抖动是指内存频繁分配和回收,导致内存曲线呈锯齿状波动,可能导致应用页面卡顿或响应缓慢。常见的内存抖动场景及解决方案:
2.1.1 频繁创建短生命周期对象
在循环或频繁调用的方法中创建大量短生命周期的对象,如字符串拼接、对象频繁创建等。
在自定义控件的 onMeasure、onLayout、onDraw 等方法中创建对象,由于这些方法会频繁调用,导致对象频繁创建和回收。
解决方案:
- 避免在循环或频繁调用的方法中创建对象。
- 使用 StringBuilder 等高效字符串拼接方式替代加号拼接。
- 在自定义控件的绘制方法中,尽量复用对象,避免频繁创建。
- 对于需要频繁创建和销毁的对象,可以考虑使用对象池来复用对象。对象池能够减少对象的创建和销毁次数,从而降低内存抖动的发生概率。
2.1.2 系统 API 或第三方库的不合理使用
调用系统 API 或第三方库时,没有合理使用其提供的对象复用机制,导致大量对象被创建。
解决方案:
- 深入了解系统 API 和第三方库的工作原理,合理使用其提供的对象复用机制。
- 避免不必要的对象创建,如使用
Message.obtain()方法获取 Message 对象,而不是直接创建新的 Message 对象。
2.1.3 Handler 使用不当
Handler 发送大量消息,且消息处理不及时,导致消息对象堆积。
解决方案:
- 在 Handler 中处理消息时,确保及时处理并释放消息对象。
- 对于延时消息,要确保在 Activity 或 View 生命周期结束前取消未处理的消息。
- 队列优化:重复消息过滤。
- 队列优化:互斥消息取消。
- 复用消息,使用
Message.obtain()方法获取 Message 对象。 - 使用消息空闲 IdleHandler。
2.2、内存泄漏(Memory Leak)
内存泄漏是指长生命周期的对象持有短生命周期对象的引用,应用程序中的对象在不再需要时仍然被引用,导致垃圾回收器(Garbage Collector,GC)无法回收这些对象所占用的内存。内存泄漏会导致可用内存逐渐减少,最终可能导致应用程序崩溃 (OOM) 或系统变得非常缓慢。常见的内存泄露场景及解决方案:
2.2.1 静态变量持有 Activity 或 Context 引用
在静态变量中持有 Activity 或 Context 的强引用,当 Activity 或 Context 不再需要时,由于静态变量的生命周期与应用程序相同,导致这些对象无法被回收。


