当 GC 工做时,虚拟机中止其余工做。频繁地触发 GC 进行内存回收,会致使系统性能严重降低。php
在极短的时间内,分配大量的内存,而后又释放它,这种现象就会形成内存抖动。典型地,在 View 控件的 onDraw 方法里分配大量内存,又释放大量内存,这种作法极易引发内存抖动,从而致使性能降低。由于 onDraw 里的大量内存分配和释放会给系统堆空间形成压力,触发 GC 工做去释放更多可用内存,而 GC 工做起来时,又会吃掉宝贵的帧时间 (帧时间是 16ms) ,最终致使性能问题。html
Java 语言的内存泄漏概念和 C/C++ 不太同样,在 Java 里是指不正确地引用致使某个对象没法被 GC 释放,从而致使可用内存愈来愈少。好比,一个图片查看程序,使用一个静态 Map 实例来缓存解码出来的 Bitmap 实例来加快加载进度。这个时候就可能存在内存泄漏。java
内存泄漏会致使可用内存愈来愈少,从而致使频繁触发 GC 回收内存,进而致使性能降低。android
经过一个很是简单的例子来演示内存抖动。这个例子里,在自定义 View 的 onDraw 方法里大量分配内存来演示内存抖动和性能之间的关系。canvas
版本一:缓存
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); String msg = ""; for (int i = 0; i < 500; i++) { if (i != 0) { msg += ", "; } msg += Integer.toString(i + 1); } Log.d("DEBUG", msg); }
版本二:性能优化
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 500; i ++) { if (i != 0) { sb.append(", "); } sb.append(i + 1); } Log.d("DEBUG", sb.toString()); }
内存抖动的特征:从 Memory Monitor 来看,有毛刺出现。即短期内分配大量的内存并触发 GC。oracle
从 Allocation Tracker 里看,一次操做会有大量的内存分配产生。app
这个例子里,咱们简单地让点击 Settings 菜单,就产生一个 100KB 的内存泄漏。dom
private void addSomeCache() { // add 100KB cache int key = new Random().nextInt(100); Log.d("sfox", "add cache for key " + key); sCache.put(key, new byte[102400]); }
内存泄漏的特征:从 Memory Monitor 来看,内存占用愈来愈大
利用 MAT 工具进行专业分析。这是个很大的话题。几乎能够独立成几个章节来说。能够参阅 MAT 自己自带的 Tutorials 来学习。另外,这篇文章里的分析方法是个不错的开始。
一个典型的问题是 Android 系统越用越慢。这种典型地是由内存泄漏引发的。一个颇有用的解决这种问题的办法是:比较先后两个阶段的内存的使用状况。通常流程以下:
好比针对拨号盘愈来愈慢的问题,咱们能够开机后启动拨号盘,打进打出10个电话。而后抓个 HPROF 文件。接着,再打进打出10个电话,再抓一个 HPROF 文件。接着拿这两个文件对比分析,看是否是会形成电话打进打出越多,内存占用越多的状况发生。
HPROF文件:HPROF 简单地理解,就是从 jvm 里 dump 出来的内存和 CPU 使用状况的一个二进制文件。它的英文全名叫 A Heap/CPU Profiling Tool。这里有它完整的官方文档和它的历史介绍。
打开 MAT 后,会有一个 Tutorials 来教你们怎么用。这里列出几个操做步骤及其注意事项。
在咱们的示例程序里面,每次点击 Settings 菜单,都会致使一次100KB的内存泄漏。下面是咱们利用上面介绍的流程来查找内存泄漏问题。咱们先点击 5 次 Settings 菜单,而后手动触发一次 GC,再导出 HPROF 文件。接着,咱们再点击 6 次 Settings 菜单,而后手动触发一次 GC,再导出第二份 HPROF 文件。咱们拿这两份 HPROF 就能够作一些对比。
经过上图能够看到,两次操做确实致使了某些类的实例增长了。图中能够清楚地看到 byte[] 和 java.util.HashMap$HashMapEntry 两个类增长得比较明显。这样,咱们随便选择一个,经过 OQL 来查询系统中的这个内存。
从上图能够找到,本次 dump 出来的内存里,确实有不少个这个类的实例。在图上右击任何一个实例,右击,选择 Paths to GC roots ,能够找到这个实例是被谁引用的。
从上图能够看出来,这个内存是被 MainActivity 里的 sCache 引用的。经过阅读代码,咱们就能够找到这个漏洞了。即每次都往 sCache 里保存一个引用。