Android 一篇讲完Crash治理之OOM及内存检测工具

应用为什么崩溃Crash?

性能优化第一篇中的Crash治理说过:“出现Crash应用闪退和崩溃一般有三个原因。

ANR(程序无响应);

Exception(异常);

LMK(低内存杀死机制);

程序停止运行时,会用Throwable类抛出相关信息,其中主要有两类,即error和exception。error是程序无法进行拦截和处理的,如LMK也属于虚拟机抛出的错误。因此只能是防范。


LMK(Low Memory Killer )低内存杀死机制。

由于Android应用的沙箱机制,每个应用程序都运行在一个独立的进程中,各自拥有独立的Dalvik虚拟机实例,系统默认分配给虚拟机的内存是有限度的,当系统内存太低依然会触发LMK机制,即出现闪退、崩溃现象。

heap size限制:即Android默认为每个应用进程分配的最大内存,这个限制在不同的Android版本和设备上可能会有所不同。在较新的Android版本中,默认的heap size限制通常为:应用进程最多可以分配的内存为系统可用内存的一半。

也可以通过在配置清单中application设置属性android:largeHeap=”true”可以突破一定限制。


OOM(OutOfMemoryError)内存溢出错误。

事实上,OOM发生Crash的情况,它并不是导致问题的根本原因,只是一种表象。换句话说,它只是压死骆驼的最后一根稻草。真正导致Crash的有以下原因:

  1. 内存泄漏:当应用程序中的对象不再使用时,没有正确释放它们所占用的内存,导致内存泄漏。这可能是由于未及时解除对对象的引用、长时间持有大量对象或者循环引用等原因导致。

  2. 大图像或大数据处理:在Android开发中,处理大图像或大数据时需要占用大量内存。如果没有正确处理这些数据,可能会导致内存溢出。

  3. 内存抖动:频繁创建对象会导致内存分配和垃圾回收的开销增加,从而增加了内存溢出的风险。尽量避免在循环或频繁调用的代码块中创建大量临时对象。

  4. 图片缓存不当:在Android开发中,加载和显示图片是常见的操作。如果没有正确管理图片缓存,可能会导致内存溢出。建议使用合适的图片加载库,并及时释放不再需要的图片资源。

  5. 过度绘制:过度绘制指的是在屏幕上绘制过多的图形元素,导致GPU和CPU负载过高,从而导致内存不足。可以通过优化布局、减少不必要的绘制操作等方式来避免过度绘制。


GC(Garbage Collection)垃圾回收器。

GC 主要是处理堆区(Heap) ,即Java虚拟机存放对象实例的运行时内存区域。

JVM能够完成内存分配和内存回收,虽然降低了开发难度,避免了像C/C++直接操作内存的危险。但也因此导致很多Java开发者不关心内存分配,导致很多程序出现内存问题。

JVM内存的分代回收算法:内存区域划分为新生代和年老代。内存的分配是发生在年轻世代中的,当一个对象存活的时间够久的时候,它就会被复制到老年代中。

对年轻代分Eden 区和 Survive 区。大多数对象先分配到Eden区,内存大的对象会直接被分配到老年代中;Survive 区又分Form、To两个小区,一个用来保存对象,另一个是空。每次进行回收时,就把From区中的可达对象都复制到To区域中,一些生存时间长的就直接复制到了老年代。最后,清理From区的内存空间,把From空间变为To空间,To空间变为From空间。

垃圾标记算法:可达性分析算法和引用计数算法

可达性分析算法:也称为根搜索算法。这个算法的基本思想就是选定一些对象作为根GC Roots ,然后以这些"GC Roots"的对象作为起始点,向下去搜索叶节点,如果目标对象到GC Roots是连接着的,就称该目标对象是可达的,否则为不可达,也就是被回收的对象。

引用计数器算法: 为每个对象都添加一个计数器,每多一个引用指向对象,计数器就加1,当计数器为0的对象,就是可回收的对象。


内存泄露 

当某些对象程序并不会再使用它们,但它们仍处于可达状态。就意味着,它们仍然占用内存不会被GC回收,从而造成内存资源的浪费,即所谓的内存泄漏。

内存泄露是导致OOM的重要原因之一,有如下场景:

长生命周期对象持有 Activity

最常见也是最严重就是Activity对象泄漏。Activity承载了App的整个界面功能,也意味着它持有的大量资源对象都无法被回收,极其容易造成OOM。

比如:Activity 中匿名使用 Handler 实际上会导致 Handler 内部类持有外部类的引用。 Handler.SendMessage() 时 ,Message 会持有 Handler的对象实例的引用,MeassageQueue又持有 Message的引用。所以当handle发送延迟消息时,Message 并不会立即的遍历出来处理,而是进行线程阻塞,等到对应的 Message 触发时间后再处理。那么在线程阻塞的这段时间中页面销毁一定会造成内存泄漏。

另外,使用 Kotlin 可以避免类似情况发生。因为它是使用静态内部类,没有持有外部引用。

各种注册操作没有对应的反注册

类似broadcast、 Service 、常用的第三方框架 ,如EventBus。在使用的时候注意在对应的生命周期方法中进行反注册。

可以使用LifeCycle来监听Activity或Fragment的生命周期变化,通知组建做出响应。

WebView 使用不当

内存泄漏的一个隐患:使用 Webview 采用布局引用方式。

当 Activity 被关闭时,Webview 不会被 GC 马上回收,而是提交给事务进行队列处理,这样就造成了内存泄漏,导致 Webview 无法及时回收。解决办法:

override fun onDestroy() {
    webView?.apply {
        val parent = parent
        if (parent is ViewGroup) {
            parent.removeView(this)
        }
        stopLoading()
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        settings.javaScriptEnabled = false
        clearHistory()
        removeAllViews()
        destroy()
    }
}

循环引用

比较少见写出 A 持有 B,B 持有 C,C 又持有A 这样的代码,不过总还是需要注意。


Memory Monitor 工具

Android Studio自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存变换走势图。例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等,如下图所示。


Memory Analyzer 工具:

    MAT(Memory Analyzer Tool) 是一个 Java Heap 分析工具。通过分析 Java 进程的内存快照 HPROF 从众多的对象中分析在内存对象占用的大小,查看哪些对象不能被垃圾收集器回收。

(a)屏幕多次翻转,出现内存持续增高时。点击 Dump java Heap就会生成运行内存快照hprof文件。

(b)然后将APP完全退出,重新启动,打开Android Monitor 再次点击Dump java Heap 生成一份还没操作(旋转屏幕)前的内存快照hprof文件。现在就已经生成好了2份hprof文件, 一份是没有旋转过屏幕的 ,一份是旋转过屏幕多次的。
(c)然后选中Android Studio 最左边的Captures 进行将hprof文件导出。导出的时候需要选择保存的目录以及文件名。

 d)打开MAT ,导入我们的2个hprof文件Open File-->选择文件-->Leak Suspects Report-->Finish:*

可以通过检索包名,查看某个类的实例个数和所在内存数据,还可以查看被引用的内存数据。

Objects:实例个数
Shallow Heap:所占内存大小
Retained Heap:释放后能回收多少内存


LeakCanary工具:

LeakCanary 只在debug版本下检测。

这个工具是Square公司在Github开源的。主流的库像okhttp、Picasso、retrofit、Dagger等都出自Square之手。有一位在Android开发领域大佬Jake Wharton(杰克.沃顿),ButterKnife的创造者,也参与贡献了Retrofit, okhttp等。

GitHup官网https://github.com/square/leakcanary

使用方法:

    private RefWatcher setupLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }
    public static RefWatcher getRefWatcher(Context context) {
        MyApplication leakApplication = (MyApplication) context.getApplicationContext();
        return leakApplication.refWatcher;
    }

@Override
    protected void onCreate(){
        refWatcher = setupLeakCanary();
    }
@Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = MyApplication.getRefWatcher(this);//1
        refWatcher.watch(this);
    }

原理:

  • 使用ObjectWatcher来监控Android的生命周期。
  • 当Activity和Fragment被destroy以后,LeakCanary会使用弱引用(WeakReference)来监控该Activity对象。如果GC后5秒内,这个弱引用没有被回收,则可能存在内存泄露。
  • 内存泄漏检测。LeakCanary通过在后台线程中检测引用是否被清除。如果引用没有被清除,LeakCanary会将堆栈信息保存到文件系统中的.hprof文件中。
  • 使用HeapAnalyzerService在一个独立进程中解析.hprof文件,找到泄露的引用来判定是哪个实例导致的泄露。
  • 显示泄露通知。LeakCanary通过DisplayLeakService发送泄露通知给开发者。

虽然LeakCanary能够检测大多数内存泄漏问题,但它也有其局限性。例如无法检测由大容量内存分配导致的OOM问题、Bitmap内存未释放问题,以及Service中的内存泄漏等。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
OOM(Out of Memory)是指在Android应用程序中由于内存不足而导致的崩溃。当应用程序加载或创建大量的对象并且无法释放时,系统的内存资源会耗尽,从而引发OOM异常。 以下是一些常见导致OOM的原因和解决方法: 1. 内存泄漏:在Android应用程序中,内存泄漏是最常见的导致OOM的原因之一。内存泄漏指的是应用程序中的对象无法被垃圾回收器正常释放,从而占用了大量的内存。解决内存泄漏问题需要仔细检查代码,确保在不再需要对象时及时释放对其的引用,例如Activity或Fragment的引用。 2. 大图资源:加载过大的图片资源也是常见的引发OOM的原因。在处理图片时,可以使用合适的图片压缩算法、适当的缩放和裁剪操作,并在不需要时及时释放图片资源。 3. 内存占用过高的库或框架:某些第三方库或框架可能会占用大量的内存,尤其是在处理大量数据或图像时。在使用这些库或框架时,需要仔细评估其内存占用情况,并根据实际需求进行优化或选择其他替代方案。 4. 频繁的网络请求:过多的网络请求可能导致内存资源的耗尽。可以通过合理控制请求的频率、使用缓存机制来减少重复请求,以及优化网络请求的代码来减少内存占用。 5. 大量的数据缓存:如果应用程序在缓存数据时没有有效地管理和清理缓存,会导致内存资源的浪费。需要合理使用缓存策略,并在不再需要时及时清理缓存数据。 6. 内存泄漏检测工具:使用内存泄漏检测工具,如LeakCanary,可以帮助发现和解决应用程序中的内存泄漏问题。 7. 优化布局和资源:避免过度使用嵌套布局和过多的资源文件,减少布局层级和资源文件的数量,以降低内存占用。 总之,避免OOM需要开发人员在开发过程中注意内存管理,合理使用内存资源,并进行适当的优化和测试。此外,不同版本的Android系统和设备可能对内存的限制有所不同,因此也需要针对不同的环境进行测试和优化

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾阳Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值