程序性能优化以内存优化(三)上篇

阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680
本篇文章将继续从如下两个内容来介绍内存优化:php

  • [内存抖动]
  • [内存泄漏]

其实大多数App或多或少都存在必定的内存泄漏状况,这些内存泄漏可能存在于特定的运行环境时才会发生。而内存泄漏堆积会引起严重后果OOM。内存抖动是指内存频繁地分配和回收,而频繁的gc会致使卡顿,严重时和内存泄漏同样会致使OOM。html

接下来咱们一块儿讨论该如何查看以及解决这部分问题思路。java

1、内存泄漏

内存泄露是指程序中间动态分配了内存,但在程序结束时没有释放这部份内存,从而形成那部份内存不可用的状况,重启计算机能够解决,但也有可能再次发生内存泄露,内存泄露和硬件没有关系,它是由软件设计缺陷引发的。android

简单点说:应该被释放的资源没有被释放。git

一、内存泄漏的种类

1)常发性内存泄漏。发生内存泄漏的代码会被屡次执行到,每次被执行的时候都会致使一块内存泄漏。github

2)偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操做过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。因此测试环境和测试方法对检测内存泄漏相当重要。web

3) 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者因为算法上的缺陷,致使总会有一块仅且一块内存发生泄漏。好比,在类的构造函数中分配内存,在析构函数中却没有释放该内存,因此内存泄漏只会发生一次。算法

4) 隐式内存泄漏。程序在运行过程当中不停的分配内存,可是直到结束的时候才释放内存。严格的说这里并无发生内存泄漏,由于最终程序释放了全部申请的内存。可是对于一个服务器程序,须要运行几天,几周甚至几个月,不及时释放内存也可能致使最终耗尽系统的全部内存。因此,咱们称这类内存泄漏为隐式内存泄漏。api

二、为何要修复内存泄漏

少许的内存泄漏可能不会引起什么问题;可是内存泄漏累积,再多的内存也会被耗尽,最终致使OOM。缓存

2、定位内存泄漏

一、初步定位是否发生内存泄漏

借助Android Studio的Monitor查看是否发生了内存泄漏状况

经过反复的执行同一个功能,触发GC操做,观察内存先后变化状况。

image

若是内存先后未发生明显变化(增长)此时能够初步判断未发生内存泄漏。

image

好比此时内存使用状况为:21.04MB,而后咱们打开一个新的Activity,而后返回执行GC操做,观察此时的内存使用状况。

二、Monitor栏基本功能说明:

image

序号一、手动触发GC操做;

序号二、Dump Java Heap,获取当前的堆栈信息,生成一个.hprof文件,AndroidStudio会自动使用HeapViewer打开;通常用于操做以后检测内存泄漏的状况;

序号三、Start Allocation Tracking 内存分配追踪工具,用于追踪一段时间的内存分配使用状况,可以知道执行一些列操做后,有哪些对象被分配空间。通常用于追踪某项操做以后的内存分配,调整相关的方法调用来优化app性能与内存使用;

序号四、剩余可用 内存;

序号五、已经使用的内存;

三、Dump Java Heap进一步定位内存泄漏

经过Monitor栏只能初步粗略的观察是否发生内存泄漏,然而要真正的发现内存泄漏以及精肯定位内存泄漏位置还须要借助相关工具分析排查。

点击Memory Monitor的Dump Java Heap,会生成一个.hprof文件,AndroidStudio会自动使用HeapViewer打开。

image

面板说明:

面板1:

Detect Leaked Activities :检测泄漏的Activity

Find Duplicate Strings :查找重复的字符串

默认两个选项都是勾选的。

image

点击绿色箭头,此时你们会看到Leaked Activities下有一个LaunchActvity@31...的信息,没错发生了内存泄漏,稍后咱们分析如何发生的内存泄漏。

面板2:

Total Count:该类的实例个数

Heap Count:选定的Heap中实例的个数

Sizeof:每一个实例占用的内存大小

Shallow Size:全部该类的实例占用的内存大小

Retained Size:该类的全部实例可支配的内存大小

面板3:

Instance:该类的全部实例对象(左侧Total Count为15,此处就有15个对象)

Depth:深度, GC Root点到该实例的最短链路数

Dominating Size:该实例可支配的内存大小

image

此时发现面板下有个实例存在。

面板4:

Reference Tree:引用树

image

经过面板1咱们发现有一个Activity发生了泄漏。咱们能够经过Reference Tree面板就能够跟踪到该实例的引用树关系。

首先第一行咱们发现一个LaunchActivity实例存在,而后展开该实例进一步查看该实例的引用关系,第二行咱们能够看出它是被LaunchActity匿名内部类持有(this$0),这个匿名内部类实例是callBack,紧接着会发现该实例在mPermissionUtil实例中持有。

此时,咱们能够进入代码查看该callBack是什么,而后在mPermissionUtils的持有。

LaunchActivity中:

image

PermissionUitl中:

image

跟踪代码发现callback是一个接口,而后在LaunchActvity中调用了PermissionUtil的requestPermission(callback),而后将该callback赋值给PermissionUtil中成员引用,因为PermissionUtil是一个单例,而后new PermissionCallBack()匿名内部类会默认持有外部类引用,此时它将持有外部类LaunchActivity的实例,而后有赋值给了PermissionUtil中的成员引用,因此形成的内存泄漏。这种内存泄漏称之为一次性内存泄漏,只会发生一次且只会泄漏最后一次调用者。

经过使用Androd Studio自带的Dump Java Heap排查内存泄漏问题对于相对简单的泄漏场景比较适合,若是发生较为复杂的泄漏场景可能使用Dump Java Heap不太容易查找问题。此时咱们能够借助另一个工具:MAT (Memory Analyzer Tool)

四、MAT

Memory Analyzer Tool是Eclipse的一个插件,它的使用以及安装这部分资料很是多,故篇幅缘由不在展开分析介绍。

下载地址:https://www.eclipse.org/mat/downloads.php

荐:https://blog.csdn.net/u010335298/article/details/52233689

荐:https://blog.csdn.net/itachi85/article/details/77075455

image

image

五、其余

咱们也能够借助第三方检测库,在运行期间检查内存泄漏状况:LeakCanary

LeakCanary是square出品的一个检测内存泄漏的库,集成到App以后便无需关心,在发生内存泄漏以后会Toast、通知栏弹出等方式提示,能够指出泄漏的引用路径,并且能够抓取当前的堆栈信息供详细分析。

image

分析内存泄漏主要是定位GC Root,只有明白GC Root点才可以准确分析定位内存泄漏问题。

3、内存抖动

内存抖动是指内存在短期内频繁地分配和回收,而频繁的gc会致使卡顿,严重时和内存泄漏同样会致使OOM。

内存抖动为何会形成OOM这关系到Java的垃圾回收。

一、常见内存抖动场景

循环中建立大量临时对象;

onDraw中建立Paint或Bitmap对象等;

二、内存抖动后果

瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。系统花费在GC上的时间越多,进行界面绘制或流音频处理的时间就越短即便每次分配的对象占用了不多的内存,可是他们叠加在一块儿会增长Heap的压力,从而触发更多其余类型的GC。这个操做有可能会影响到帧率,并使得用户感知到性能问题。

image

4、onTrimMemory与onLowMemory

Android系统的每一个进程都有一个最大内存限制,若是申请的内存资源超过这个限制,系统就会抛出OOM错误。

onTrimMemory

因此在实际开发过程当中咱们要尽量避免内存泄漏与内存抖动以外,还要格外注意内存使用状况。根据《Manage Your App's Memory》,咱们能够对内存的状态进行监听,咱们的Application、Acivity、Service、ContentProvider与Fragment都实现了ComponentCallbacks2接口。因此可以重写onTrimMemory与onLowMemory函数。

image

onTrimMemory的参数是一个int数值,表明不一样的内存状态:

TRIM_MEMORY_RUNNING_MODERATE:

你的应用正在运行而且不会被列为可杀死的。可是设备此时正运行于低内存状态下,系统开始触发杀死LRU Cache中的Process的机制。

TRIM_MEMORY_RUNNING_LOW:

你的应用正在运行且没有被列为可杀死的。可是设备正运行于更低内存的状态下,你应该释放不用的资源用来提高系统性能。

TRIM_MEMORY_RUNNING_CRITICAL:

你的应用仍在运行,可是系统已经把LRU Cache中的大多数进程都已经杀死,所以你应该当即释放全部非必须的资源。若是系统不能回收到足够的RAM数量,系统将会清除全部的LRU缓存中的进程,而且开始杀死那些以前被认为不该该杀死的进程,例如那个包含了一个运行态Service的进程。

当应用进程退到后台正在被Cached的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:

TRIM_MEMORY_BACKGROUND:

系统正运行于低内存状态而且你的进程正处于LRU缓存名单中最不容易杀掉的位置。尽管你的应用进程并非处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其余进程了。你应该释放那些容易恢复的资源,以便于你的进程能够保留下来,这样当用户回退到你的应用的时候才可以迅速恢复。

TRIM_MEMORY_MODERATE:

系统正运行于低内存状态而且你的进程已经已经接近LRU名单的中部位置。若是系统开始变得更加内存紧张,你的进程是有可能被杀死的。

TRIM_MEMORY_COMPLETE:

系统正运行于低内存的状态而且你的进程正处于LRU名单中最容易被杀掉的位置。你应该释听任何不影响你的应用恢复状态的资源。

TRIM_MEMORY_UI_HIDDEN:

UI不可见了,应该释放占用大量内存的UI数据。

好比说一个Bitmap,咱们缓存下来是为了可能的(不必定)再次显示。可是若是接到这个回调,那么仍是将它释放掉,若是回到前台,再显示会比较好。

onLowMemory

这个函数看名字就是低内存。这个函数的回调意味着后台进程已经被干掉了。这个回调能够做为4.0兼容onTrimMemory的TRIM_MEMORY_COMPLETE来使用

若是但愿在其余组件中也能接收到这些回调可使用上下文的registerComponentCallbacks注册接收,

unRegisterComponentCallbacks反注册

5、OutOfMemeory

OOM就是申请的内存超过了Heap的最大值。

image

OOM的产生不必定是一次申请的内存就超过了最大值,致使oom的缘由基本上都是通常状况,咱们的不良代码平时”积累”下来的。

咱们知道Android应用的进程都是从一个叫作Zygote的进程fork出来的。而且每一个应Aandroid会对其进行内存限制。咱们能够查看:

image

6、有效减小内存占用的建议

一、使用Android优化事后的集合

在Android开发时,咱们使用的大部分都是Java的api。其中咱们常常会用到java中的集合,好比HashMap。

使用HashMap很是舒服,可是对于Android这种内存敏感的移动平台,不少时候使用这些Java的API并不能达到更好的性能,相反反而更消耗内存,因此针对Android,google也推出了更符合本身的API,好比SparseArray、ArrayMap用来代替HashMap在有些状况下能带来更好的性能提高。

注意:此处仅考虑内存占用状况,而且在必定的长度的数据集,并非适合全部场景下。

二、集合初始长度

如:HashMap,他的默认长度为16,负载因子为0.75,若是咱们知道要存放数据的长度如5,此时最合适的HashMap的初始容量为:5/0.75 = 7;

故:HashMap map = new HashMap(7)

三、Bitmap

Bitmap能够说是一个内存中的大胖子,做为如今Android开发程序是比较幸福,有不少关于图片加载优秀的库,如Glide。

有关于Bitmap的优化咱们会在后续单独专题中介绍,故不在此处展开介绍。

荐:《Handling Bitmaps》

四、try{}cacth(Error){}

对高风险OOM代码块如展现高清大图等进行try catch,在catch块加载非高清的图片并作相应内存回收的处理。注意OOM是OutOfMemoryError,不能使用Exception进行捕获。

五、解决全部内存泄漏问题

少许的内存泄漏可能不会带来较为明显的影响,可是内存泄漏堆积的后果是很是严重的,再多的内存也会被耗尽,最终致使OOM发生。

六、避免内存抖动

尽可能避免在循环体或者频繁调用的函数内建立对象,应该把对象建立移到循环体外。

另外还有一个经典的String拼接建立大量小的对象形成的内存抖动。

有时会发现频繁的调用Log打印日志,App会变卡顿。

Log.i(TAG,width+”x”+height);

这里会产生2个新对象,width+”x”与width+”x”+height。

而TAG与x是编译时就存在的字符串常量池,因此不算新对像。

因此通常来讲咱们会对日志输出Log进行控制,或者使用StringBuilder进行优化。

七、onTrimMemory根据不一样的内存状态作相应处理

对于未实现ComponentCallbacks2组件,咱们须要为其注册ComponentCallbacks2。

image

7、总结

性能优化是一个长期实践过程;大多数问题都是由通常问题形成的,而后这部分通常问题的积累最终会引起严重后果;压死骆驼的可能就是最后一根稻草。在项目实际开发过程当中:要特别注意内存泄漏与内存抖动的场景,注意配合使用onTrimMemory完成内存的管理工做。

阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680
原文连接:https://www.jianshu.com/p/607...

相关文章
相关标签/搜索