1.内存抖动
内存抖动是Android性能优化中内存优化的一种情况。内存抖动主要是由于频繁的创建和销毁对象导致的。
在程序里,每创建一个对象,就会有一块内存分配给它。每分配一块内存,程序的可用内存就少一块。当程序被占用的内存达到一定临界程度,垃圾回收器GC(Garbage Collector)就会出动,来释放掉不再被使用的那部分内存。
比如Android里的View.onDraw()方法在每次需要重绘的时候都会被调用,这就意味着,如果在 onDraw()里写了创建对象的代码,在界面频繁刷新的时候,就会频繁创建出一大批只被使用一次的对象,这就会导致内存占用的迅速攀升;然后很快,可能就会触发GC的回收动作,也就是这些被创建出来的对象被GC回收掉。
垃圾内存太多了就被清理掉,这是Java的工作机制,这不是问题。问题在于,频繁创建这些对象会造成内存不断地攀升,在刚回收了之后又迅速涨起来,那么紧接着就是又一次的回收,这样往复下来,最终导致一种循环,一种在短时间内反复地发生内存增长和回收的循环。这种循环往复的状态就叫做内存抖动Memory Churn。
所以,内存抖动实际上就是内存频繁的分配或回收而导致内存不稳定的一种现象。如果说分配的频率大于回收的频率,那么最终就会导致OOM的发生。
内存抖动不处理的话,最终可能导致手机卡顿,甚至OOM。
2.举例
以字符串拼接为例:
public String addStr(String[ ] values) {
String result = null;
for(int i = 0; i < values.length; i++) {
result += values[i];
}
return result;
}
String字符串拼接时会创建StringBuilder对象。因为字符串使用+进行拼接时,每执行一次result += values[i];,都等同于执行一次result = new StringBuilder().append(values[i]).append(result).toString();,也就是说每使用一次+就会使用new StringBuilder()去创建一个StringBuilder对象。这样就造成了在for循环里短时间内创建大量对象,而这些对象在一次for循环完成后就没有价值了,GC就会对它们进行回收,因此就很可能造成内存抖动。
3.内存抖动的危害
①导致程序卡顿
内存抖动会导致程序卡顿。因为内存抖动伴随着频繁的GC,而GC有一个特性,就是STW(stop the world),STW就会使程序出现卡顿。
②导致OOM
内存抖动会产生内存碎片,而内存碎片过多以后,在新建一个大对象时,就会出现OOM(内存碎片会造成无法提供连续的内存空间)
注意:Android8.0以前,内存抖动会导致OOM,而在Android8.0以后,ART支持标记并整理,就不会导致OOM了。
4.profiler检测内存抖动
大循环中创建对象、自定义View的onDraw()方法中创建对象(屏幕绘制与动画执行时会频繁调用onDraw())是最常见的引起内存抖动的场景。
内存抖动可采用Android Profiler进行检测,可截取某段时间进行对象分析,查看哪些对象被频繁创建。
对上面的例子利用Android Profiler进行检测:
Record截取一段时间进行分析:
可以看到创建了大量的StringBuilder对象,也就是在循环中频繁创建对象,GC回收频繁,导致内存抖动。
针对大循环引起的内存抖动,解决办法就是将对象创建放到循环外,对于无法避免的创建对象情况,可采用对象池模型进行缓存,复用对象,需注意用完后要手动释放对象池中对象。
再看看自定义View的onDraw()方法中创建对象引起内存抖动的情况:
public class IOSStyleLoadingView extends View{
……
@override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
……
Path path = new Path();
path.moveTo(100,100);
……
}
}
由于屏幕绘制与动画执行时会频繁调用onDraw()方法,因此在onDraw()方法中创建对象就很容易引起内存抖动。
使用profiler很容易就定位出引起内存抖动的原因是在onDraw()方法里创建对象了。
这里解决办法也很简单,就是把Paint和Path的创建移出onDraw()方法就可以了。