继续创造,加快成长!这是我参与「日新方案 10 月更文应战」的第1天,点击查看活动详情
内存颤抖概念
在程序里,每创立一个目标,就会有一块内存分配给它;每分配一块内存,程序的可用内存也就少一块;当程序被占用的内存达到一定临界程度,GC 也便是废物收回器(Garbage Collector)就会出动,来开释掉一部分不再被运用的内存。
Android 里的 View.onDraw() 办法在每次需求重绘的时分都会被调用,这就意味着,假如你在 onDraw() 里写了创立目标的代码,在界面频频改写的时分,你就也会频频创立出一大批只被运用一次的目标,这就会导致内存占用的敏捷攀升;然后很快,或许就会触发 GC 的收回动作,也便是这些被你创立出来的目标被 GC 收回掉。
[废物内存]太多了就被清理掉,这是 Java 的作业机制,这不是问题。问题在于,频频创立这些目标会形成内存不断地攀升,在刚收回了之后又敏捷涨起来,那么紧接着便是又一次的收回,对吧?这么往复下来,最终导致一种循环,一种在短时间内重复地发生内存添加和收回的循环。
这种循环往复的状况就像是水波纹的颤抖相同,它的专业称号叫做 Memory Churn,Android 的官方文档里把它翻译做了内存颤抖。
简单来说便是: 在程序需求目标的时分,在堆当中分配出来一块空间,运用完毕今后, GC 帮咱们清理掉这片内存空间,假如频频的一向继续上述操作,就会引起内存颤抖。
内存颤抖原因剖析
Android Studio提供了一个 profile的东西,可以帮助咱们剖析内存状况,在studio的上不有一个表盘的图标
点击红框图标,然后就会运转当时项目,选中连接的手机,然后在studio的底部就会呈现一个Android Profile的东西项(如下图)。
咱们会看到有CPU,MEMORY,NETWORK三个选项,咱们要监控的是内存,所以点击MEMORY,会进入下图
在这张图中咱们可以看到咱们当时应用所占的内存total,咱们这儿要点说一下左上角的三个按钮分别是:
- GC手动废物收回
- 收集当时页面的内存状况并生成hprof文件
- 记载当时操作的内存状况 首先咱们调用GC是当时内存处于稳定状况,然后点击赤色按钮开始记载内存状况,然后咱们开始在手机上进行操作(一般这个时分会发现平稳的曲线开始波动),然后咱们点击中止按钮,回呈现下图的状况。
框框内是咱们当时记载的操作区间,当咱们点击中止按钮的时分, 在Class Name这个框内,会生成当时记载区间(操作过程)的堆信息。
咱们发现咱们的曲线有一个向上的陡坡,说明咱们的操作形成了很多的内存分配,经过下面堆信息咱们来找一下是什么形成了很多的内存分配。
咱们发现堆信息是从大到小排列的,而第一条是系统的imageView,明显这儿有问题,点击进去发现有很多的imageView目标,点击其中的一个,右下角的框框会显示该目标的具体位置信息。至此咱们找到了形成内存颤抖的罪魁祸首。
当然,这儿的内存颤抖是我人为加上去的,比较明显,但是原理是相同的。
内存颤抖方案处理
1.调集类
调集类假如仅仅有添加元素的机制,而没有相应删去元素机制,这样就会形成内存被占用,假如这个类是全局性变量(比如类中有静态属性,全局性的map等即有静态引证或final一向指向它)。那么没有相应删去机制,很或许导致调集所占内存只增不减。 处理办法:在运用调集类时,添加删去元素机制,并恰当调用减少调集所占内存。
2.单例形式
不正确运用单例形式,也会引起内存走漏单例目标在初始化后将在JVM的整个生命周期存在(以静态变量方法),假如单例目标持有外部目标的引证,那么这个外部目标就会一向占用着内存,或许导致内存走漏(取决于这外部目标是否一致有用)。 处理办法:单例目标中避免含有不是一向都有用的外部目标引证。
3.Android组件或特殊调集目标的运用
BraodcastReceiver ,ContentObserver,fileObserver,Cursor,Callback等在Activity onDestory或许某类生命周期完毕之后一定要unregistere或许close掉,不然这个Activity类会被system强引证,不会被收回。不要直接对Activity进行直接引证作为成员变量,假如不得不这么做,调用private WeakPeferense mActivity 来做,相同的,对与Service等其他有自己生命周期的目标来说,直接引证都需求考虑是否会存在内存走漏的或许。
4.Handler
要知道,只要Handler 发送的Message尚未被处理,则该Message及发送它的Handler目标将被线程MessageQueue一向持有。由于Handler归于TLS(Thread Local Storage)变量,生命周期和Activity是不一致的。因而这种完成方法一般很难保证跟view或许Activity的生命周期保持一致,故很简单导致无法正确开释。如上所述,Handler运用要特别小心,不然很或许内存走漏。 处理办法:在view 或许Activity生命周期完毕前,保证Handler已没有未处理的消息(特别是延时消息)。
5.Thread 内存走漏
线程也是形成内存走漏的一个重要源头,线程产生内存走漏的主要原因在于线程生命周期不可控,比如线程是Activity的内部类,则线程目标中保存了Activity的一个引证,当线程的run函数耗时较长没有完毕时,线程目标是不会被毁掉的,因而它所引证的老的Activity就呈现了内存走漏问题。处理办法:1.简化线程run函数履行的使命,使他在Activity生命周期完毕前,使命运转完。2.为Thread添加吊销机制,当Activity生命周期完毕时,将Thread的耗时使命吊销(笔者引荐这种)。
6.一些不良代码形成的内存压力
有些代码并不形成内存走漏,但是他们是对没运用的内存没进行有用及时的开释,或是没有有用的运用已有的目标而是频频的请求新内存。
(1) Bitmap 没调用recycle()
Bitmap 目标在不运用时,咱们应该先调用recycle()开释内存,然后才置空,由于加载bitmap目标的内存空间,一部分是java的,一部分是c的(由于Bitmap分配的底层是经过jni调用的,Android的Bitmap底层是运用skia图形库完成,skia是用c完成的)。这个recycle()函数便是针对c部分的内存开释。
(2)结构Adapter时,没有运用缓存的convertView。 处理办法:运用静态holdview的方法结构Adapter。
这样到这儿内存颤抖和内存走漏的发现,定位以及处理办法以说明完毕。
内存颤抖一向是Android功能优化的重要环节,Android的功能优化除了内存颤抖导致的卡顿外,还有布局优化、卡顿优化、发动优化等等。Android功能优化也是大厂面试的必备技术,面试也是被常常问及到的问题。
内存颤抖的处理技巧
要点关注:循环
或许频频调用
的当地!! 由于内存颤抖
便是 内存
在被不断地收回
及分配
, 这种状况的话经常是 呈现在 循环
或许频频调用
的当地