优化方向
内存问题
- 泄漏
- 大对象
- 抖动
页面加载速度
冷热启动
页面卡顿
- UI刷新框架:事件驱动型,虽然便于管理。
- 各个链路耗时优化。
- GC带来暂停。
- 对象池
- UI复用
定位大对象、定位内存泄漏点、定位抖动代码点(异步事件、同步事件造成的抖动,掺杂业务复杂度的抖动点最难排查)
一、内存耗用情况分析
1、我们硬件设备的易用性差,因为设备内存小,引发频繁的GC,每次GC占用至少50ms,这个过程会暂停APP进程,导致UI短暂无响应。紧接着如何定位引起抖动的代码位置呢?好难啊!结合Monitor我们能知道点菜大流程存在抖动,但是流程是个复杂的、异步流程,人肉看代码分析,是不可能的,不同于之前代码量少、存在For循环频繁创建对象等场景。
2、如何定位抖动代码位置呢?我们先从定位 抖动对象开始,也就是过程中产生的对象,哪些是有效对象?哪些是无效对象?我们再看是否存在大量耗用内存的无效对象。然后再从对象找到调用链,定位代码位置。
3、如何定位抖动对象呢?我们已知的能够通过record alloc获取一段时间内总共分配了哪些对象、分配的多少次,这是个差量数据!能够通过dumpheap hprof获取当前时间点的内存快照。通过alloc是看不出哪些是临时对象!通过hprof可悲的是内存快照的现在式,抖动是过去式。如果alloc 和 hprof 配合呢? 分析思路又是什么样的呢?
4、不卖官司了,思路如下图。采集两次hprof 和 1次alloc,alloc在两次hprof中间,并且alloc过程要尽可能多的触发抖动,目的是为了扩大特征点,减少干扰。对alloc、hprof进行top排序,两次hprof每一项的的差值应该等于alloc中对应的该项,若存在很大偏差,说明该项存在大量临时对象,为GC扫描加重了负担。
5、对比发现,char[] 耗用内存 11M,String耗用5M。均属于临时对象。下一步,我们看调用链路在哪个点产生的,找到点后,发现都经过了String.format() 和 StringBuilder.append() 。看源码后知道了StringBuilder内部经历了 String -> char[] -> String 2个转化过程,每次转化,都会申请一块新内存,导致产生至少2块同等大小的新内存,新内存都是临时使用的,也就是临时垃圾,会被GC掉的。
6、哎妈呀,终于可以收割了,为保险起见,我又做了三个对比实验,验证StringBuilder.toString()的过程是如何的耗用内存。
7、实验代码如下:
1)对比结果好可怕:
第一个GC掉10K,第二个GC掉49K,第三个GC掉87K。第二个过程,比第一个多产生40kb。
04-03 16:51:08.397 12602-12608/? D/dalvikvm: GC_EXPLICIT freed <1K, 18% free 3065K/3708K, paused 1ms+1ms, total 17ms
04-03 16:51:13.997 12602-12608/? D/dalvikvm: GC_EXPLICIT freed 10K, 18% free 3065K/3708K, paused 6ms+2ms, total 50ms
04-03 16:51:14.517 12602-12608/? D/dalvikvm: GC_EXPLICIT freed <1K, 18% free 3065K/3708K, paused 1ms+1ms, total 14ms
04-03 16:51:01.167 12602-12608/? D/dalvikvm: GC_EXPLICIT freed <1K, 18% free 3065K/3708K, paused 1ms+1ms, total 15ms
04-03 16:51:07.447 12602-12608/? D/dalvikvm: GC_EXPLICIT freed 49K, 18% free 3065K/3708K, paused 6ms+3ms, total 53ms
04-03 16:51:07.987 12602-12608/? D/dalvikvm: GC_EXPLICIT freed <1K, 18% free 3065K/3708K, paused 2ms+1ms, total 15ms
04-03 17:20:15.067: D/dalvikvm(23173): GC_EXPLICIT freed 87K, 15% free 3062K/3584K, paused 2ms+2ms, total 24ms
04-03 17:05:15.217: D/dalvikvm(19590): GC_EXPLICIT freed <1K, 15% free 3062K/3572K, paused 2ms+2ms, total 24ms
04-03 16:57:46.707 17926-17926/? D/MainActivity: builder length 19491
04-03 16:57:47.467 17926-17926/? D/MainActivity: msg length 19480
04-03 16:57:48.027 17926-17926/? D/MainActivity: builder length 19491
https://blog.csdn.net/liyanjing1987/article/details/38317787
StringBuilder 是否消耗内存
public String toString() {
if (count == 0) {
return "";
}
return StringFactory.newStringFromChars(0, count, value);
}
8、我们代码里,存在大量StringBuilder 和 Format的过程,每个过程,耗用3倍临时内存,经过以下过程,要耗用10倍临时内存。每次GC引发至少50ms的暂停!我们UI响应慢的主要原因。
CalculateManager.calculate
---》 orderCalculateParam.toString(); @1
CalculateLogImpl LOGGER.info("@calculate - CalculateManager {} {}", new Object[]{paramLog, orderCalculateResult});
———》 msg = msg.replaceAll("\\{\\}", "%s");(可忽略不计算)
———》 msg = String.format(msg, objects); @1 @1 @1 String - subString - value[] - String
---》 MessageFormat.format(FORMAT_EXPRESSION, CashierDeskPrefix.M, content); @1 @1
LogMonitor
———》 sb.append(date).append(" | ").append("[").append(DEVICE_TYPE).append("]").append(tag).append(" : ").append(info).append("\n"); @1
———》 sb.toString() @1
———》 bufferedSink.writeUtf8(str); @1
9、解决办法呢?目标是,减少临时垃圾。我们的场景是每个操作到要日志序列化到本地,日志多了之后,对日志管理的过程存在大量格式化拼接,比如StringBuilder、Format,解决办法有两个思路 A\减少类型转化 B\char[]内存复用。
10、char[] 内存复用,及时有应该如何使用呢?