本文在 B 站有对应的视频,如果你喜欢看视频版本,可以点 这里 去哔哩哔哩观看,或者点 这里 去 YouTube 观看。
面试官:你做过性能优化是吧。
面试者:嗯是的,在卡顿和耗电问题上做过挺多事。
面试官:内存抖动的解决方案你有了解过吗?
面试者:内存什么?
面试官:内存抖动。没有听过吗?
面试者:……没有。
面试官:呼(摇头)。年轻人还是要敬畏技术啊,要持续学习啊。
面试者:我……嗯……
大家好,我是扔物线朱凯。
看起来很酷的词总是容易吸引眼球,比如「双亲委托」,比如「责任链」,比如——「内存抖动」。吸引眼球就意味着会有更多点击,而点击量是内容创作者最爱的东西,所以这些名字很酷的词就很自然地会受到各种技术文章的偏爱,因为你写这些文章可以得到更多的流量,这是无可厚非的。强调一下,这是无可厚非的。但有些作者对于流量的追求过于偏执,什么东西都喜欢过分地吹一下。
大家作为读者,看文章的时候要有辨别力,不要被这些流量词带偏,更不要被某些流量贩子们骗得去加到自己的面试题里来考验你的面试者是否读过某篇文章,这是没有价值的。
啊啊?「内存抖动」这个概念竟然没价值?
不是的 ,我不是说内存抖动的概念没价值。哎算了,我先说一下什么是内存抖动吧:
在程序里,每创建一个对象,就会有一块内存分配给它;每分配一块内存,程序的可用内存也就少一块;当程序被占用的内存达到一定临界程度,GC 也就是垃圾回收器(Garbage Collector)就会出动,来释放掉一部分不再被使用的内存。Android 里的 View.onDraw() 方法在每次需要重绘的时候都会被调用,这就意味着,如果你在 onDraw() 里写了创建对象的代码,在界面频繁刷新的时候,你就也会频繁创建出一大批只被使用一次的对象,这就会导致内存占用的迅速攀升;然后很快,可能就会触发 GC 的回收动作,也就是这些被你创建出来的对象被 GC 回收掉。垃圾内存太多了就被清理掉,这是 Java 的工作机制,这不是问题。问题在于,频繁创建这些对象会造成内存不断地攀升,在刚回收了之后又迅速涨起来,那么紧接着就是又一次的回收,对吧?这么往复下来,最终导致一种循环,一种在短时间内反复地发生内存增长和回收的循环。
这种循环往复的状态就像是水波纹的颤动一样,它的专业称呼叫做 Memory Churn,Android 的官方文档里把它翻译做了内存抖动。所以内存抖动其实并不是我们的内存在整体地进行摇晃这样神奇的事情,
而仅仅是类似有一根搅拌棒轻轻地在内存的边界上进行搅动的样子——其实翻译成「内存搅动」好像也行哈?
我们也可以通过 Android Studio 的 Memory Profiler 来更直观地观察到这种现象:
内存的回收虽然很快,时间成本很低,但终究是有时间成本的。一两次内存回收不容易被用户察觉,但多次内存回收行为集中在短时间内爆发,这就造成了比较大的界面卡顿的风险。这也是为什么 Android 在官方文档和 Android Studio 里都建议我们尽量避免在 onDraw() 里创建对象。
同样的道理,不只是在 onDraw(),在次数比较大的循环里创建对象,同样会导致内存抖动。不过因为在实践中,我们在 onDraw() 里创建的对象往往是绘制相关的对象,而这些对象又经常会包含通往系统下层的 Native 对象的引用,这就导致在 onDraw() 里创建对象所导致的内存回收的耗时往往会更高,直白地说就是——界面更卡顿。
另外呢内存抖动有时候也会抖着抖着就变成内存溢出了,这就是更严重的情况,因为内存溢出的直接结果就是软件崩溃。
所以,「内存抖动」这个概念有价值吗?
当然有价值了,因为这个词帮助我们很形象地去形容了程序中的一种现象。对吧?增加、回收、增加、回收。
那么我在开头说的「没价值」,指的是什么呢?
我说的不是「内存抖动」的概念没价值,而是追求对这个概念本身的了解是没价值的。确切地说是,面试官们企图用是否知道「内存抖动」这样的词来考察面试者是否具有高开发水平这样的面试策略,是没价值的。内存优化是一个很复杂很细碎的话题,而在 onDraw() 中避免创建对象只是其中的冰山一角。而且就算在这冰山一角里,重点也只在于「避免内存增长」,而不是「避免内存抖动」。内存抖动只是一种具体的表面现象而已,而这种现象背后的原因,除了在 onDraw() 中创建对象,也很少再有其他场景了。那么这种场景限定极强的词被专门列出来在面试题里,又有什么意义呢?
我不是在说你面试的时候不应该问内存抖动,而是说你的注意力一定不能放在这个词上。听没听说过内存抖动并不重要,知道内存抖动的原因和解决方案才重要。
嗯……没听过内存抖动,怎么会知道它的原因和解决方案?
他没听过你可以告诉他呀!不是不能问内存抖动,而是如果你问了之后对方表示没听过,你应该进一步引导,比如你问他:那么,Android 官方建议我们不要在 onDraw() 里创建对象你知道吗?你知道为什么吗?如果他立即回答这会导致频繁触发内存回收,那不是证明他其实懂原理的吗?这时候你再告诉他,这就叫内存抖动,就行了。
而且也不要局限于这一个词,你还可以继续问:为什么在 onDraw() 里创建对象导致的结果是内存抖动而不是内存溢出?这种对于能力的考察比对词汇的考察重要多了。其实我也不是很确定这个问题是不是每个喜欢问内存抖动的面试官们都能回答上来,但这样问才是考察程序员能力的最好方式。
如果你在面试时问了内存抖动,但在对方表示没听过之后,你就不再进行任何的引导而是直接给对方扣了分,那你其实相当于在问:我这里有一个高端词汇,您听过吗?
我在上课的时候,经常跟学员强调学技术要学本质,因为有些地方我讲得比较深我怕他们不学。说到我的课,有想学 Android 高级进阶系列化知识的,扫码咨询。
我怎么觉得我的广告有点六学?过渡一点也不优雅,强行转弯,强势恰饭。下次改进下次改进。
说回到本质,学技术要学本质,但内存抖动并不是任何技术的本质。其实我今天也并不是在聊内存抖动这个词本身,而是想表达一种观点:
我们学技术,应该学得深,而且应该足够深,但不要被各种花里胡哨的词吓到,也不要被它们带着跑,我们要有自己的知识体系,有自己的成长逻辑。
我是扔物线,我不和你比高低,我只助你成长。我们下期见。
本文使用 mdnice 排版