目录
2.2.1 静态变量持有Activity或Context引用
一、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 虚拟机申请内存最大内存是有限制的,不同设备申请的最大内存是不一样的。
二、内存的三大问题
1、内存抖动:内存波动图形呈 锯齿张、GC导致卡顿。
2、内存泄漏:在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小。
3、内存溢出:即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对象。 - 使用消息空闲ldleHandle
2.2、内存泄漏(Memory Leak)
内存泄漏是指长生命周期的对象持有短生命周期对象的引用,应用程序中的对象在不再需要时仍然被引用,导致垃圾回收器(Garbage Collector,GC)无法回收这些对象所占用的内存。内存泄漏会导致可用内存逐渐减少,最终可能导致应用程序崩溃(OOM)或系统变得非常缓慢。常见的内存泄露场景及解决方案:
2.2.1 静态变量持有Activity或Context引用
在静态变量中持有Activity或Context的强引用,当Activity或Context不再需要时,由于静态变量的生命周期与应用程序相同,导致这些对象无法被回收。
解决方案:
- 避免在静态变量中持有Activity或Context的强引用。
- 如果确实需要持有Context,考虑使用Application Context或弱引用(WeakReference)。
2.2.2 未取消的回调或监听器
在Activity或Fragment中注册回调或监听器,但未在适当的生命周期方法中取消注册,导致Activity或Fragment被销毁后,回调或监听器仍持有其引用。
解决方案:
- 在Activity或Fragment的onDestroy或onDetach方法中取消所有回调和监听器注册。
- 使用View的观察者模式时,确保在View不再需要时解除观察。
2.2.3 非静态内部类持有外部类引用
非静态内部类默认持有其外部类的引用,如果非静态内部类被长期持有(如作为静态变量的成员),则外部类也无法被回收。
解决方案:
- 将内部类声明为静态内部类,并通过构造方法传递必要的Context或其他引用。
- 如果内部类需要访问外部类的成员,考虑使用弱引用持有外部类的引用。
2.2.4 Timer或Handler未正确取消
使用Timer或Handler时,如果未正确取消定时任务或消息,当Activity或Fragment销毁后,这些定时任务或消息仍可能持有其引用。
解决方案:
- 在Activity或Fragment的onDestroy或onDetach方法中取消所有Timer任务。
- 对于Handler,确保在Activity或View生命周期结束前处理完所有消息,并调用handler.removeCallbacksAndMessages(null)来取消所有回调和消息。
2.2.5 Bitmap未及时回收
在加载大图片或处理图片时,如果未及时回收Bitmap对象,可能导致内存泄露。
解决方案:
- 在不再需要Bitmap时,及时调用bitmap.recycle()方法回收Bitmap。
- 使用图片加载库(如Glide、Picasso)时,这些库通常会自动管理Bitmap的回收,但仍需注意避免在Activity或Fragment销毁后继续加载图片。
2.2.6 资源文件未关闭
在处理文件、数据库连接等资源时,如果未正确关闭这些资源,也可能导致内存泄露。