应用为什么崩溃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的有以下原因:
-
内存泄漏:当应用程序中的对象不再使用时,没有正确释放它们所占用的内存,导致内存泄漏。这可能是由于未及时解除对对象的引用、长时间持有大量对象或者循环引用等原因导致。
-
大图像或大数据处理:在Android开发中,处理大图像或大数据时需要占用大量内存。如果没有正确处理这些数据,可能会导致内存溢出。
-
内存抖动:频繁创建对象会导致内存分配和垃圾回收的开销增加,从而增加了内存溢出的风险。尽量避免在循环或频繁调用的代码块中创建大量临时对象。
-
图片缓存不当:在Android开发中,加载和显示图片是常见的操作。如果没有正确管理图片缓存,可能会导致内存溢出。建议使用合适的图片加载库,并及时释放不再需要的图片资源。
-
过度绘制:过度绘制指的是在屏幕上绘制过多的图形元素,导致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中的内存泄漏等。