Android性能优化之被忽视的Memory Leaks
2016-05-05 16:48
453 查看
起因
那天无意中我发现了一个奇怪的现象,随着我点开我们App的页面,Memory Monitor中显示占用的内存越来越多(前面的页面已经finish掉了)。咦?什么鬼?经过
有了问题就解决嘛,俗话说的好,有bug要上,没有bug写个bug也要上。那到底是是什么问题会引起这个现象呢?Android中内存相关的问题无非就是这么几点:Memory Leaks 内存泄漏Memory Churn 内存抖动OutOfMemory 内存溢出阿西吧,仔细想想怎么这么像内存泄漏呢。那到底是不是呢?那我们就一点一点分析一下呗。内存相关数据
关于内存我4000们可能想了解的数据大概有三点:总内存private String getTotalMemory() { String str1 = "/proc/meminfo";// 系统内存信息文件 String str2; String[] arrayOfString; long initial_memory = 0; try { FileReader localFileReader = new FileReader(str1); BufferedReader localBufferedReader = new BufferedReader( localFileReader, 8192); str2 = localBufferedReader.readLine();// 读取meminfo第一行,系统总内存大小 arrayOfString = str2.split("\\s+"); for (String num : arrayOfString) { Log.i(str2, num + "\t"); } initial_memory = Integer.valueOf(arrayOfString[1]).intValue() * 1024;// 获得系统总内存,单位是KB,乘以1024转换为Byte localBufferedReader.close(); } catch (IOException e) { } return Formatter.formatFileSize(getBaseContext(), initial_memory);// Byte转换为KB或者MB,内存大小规格化 }系统当前可用内存
private String getAvailMemory() { // 获取android当前可用内存大小 ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); am.getMemoryInfo(mi); //mi.availMem; 当前系统的可用内存 return Formatter.formatFileSize(getBaseContext(), mi.availMem);// 将获取的内存大小规格化 }我们可以使用的内存每一个Android设备都会有不同的RAM总大小与可用空间,因此不同设备为app提供了不同大小的heap限制。你可以通过调用getMemoryClass())来获取你的app的可用heap大小。如果你的app尝试申请更多的内存,会出现OutOfMemory的错误。在一些特殊的情景下,你可以通过在manifest的application标签下添加largeHeap=true的属性来声明一个更大的heap空间。如果你这样做,你可以通过getLargeMemoryClass())来获取到一个更大的heap size。然而,能够获取更大heap的设计本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。不要轻易的因为你需要使用大量的内存而去请求一个大的heap size。只有当你清楚的知道哪里会使用大量的内存并且为什么这些内存必须被保留时才去使用large heap. 因此请尽量少使用large heap。使用额外的内存会影响系统整体的用户体验,并且会使得GC的每次运行时间更长。在任务切换时,系统的性能会变得大打折扣。另外, large heap并不一定能够获取到更大的heap。在某些有严格限制的机器上,large heap的大小和通常的heap size是一样的。因此即使你申请了large heap,你还是应该通过执行getMemoryClass()来检查实际获取到的heap大小。
private String getAllocationMemory() { // 获取系统分配的内存大小 ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); //开启了android:largeHeap="true",米4系统能分配的内存为512M,不开启为128M //return am.getLargeMemoryClass()+""; //return am.getMemoryClass()+"";}
Java中的四种引用
开始分析之前,有必要先了解下Java的内存分配与回收。Java的数据类型分为两类:基本数据类型、引用数据类型。基本数据类型的值存储在栈内存中,而引用数据类型需要开辟两块存储空间,一块在堆内存中,用于存储该类型的对象;另一块在栈内存中,用于存储堆内存中该对象的引用。其中引用类型变量分为四类:强引用最常用的引用形式。把一个对象赋给一个引用类型变量,则为强引用。只要一个引用是强引用,则垃圾回收器永远都无法回收这个对象的内存空间,除非JVM终止。软引用当内存资源充足的时候,垃圾回收器不会回收软引用对应的对象的内存空间;但当内存资源紧张时,软引用所对应的对象就会被垃圾回收器回收。//创建一个Student类型的软引用SoftReference<Student> sr = new SoftReference<Student>(new Student());弱引用不管JVM内存资源是否紧张,只要垃圾回收器运行,弱引用所对应的对象就会被释放。虚引用虚引用等于没有引用,无法通过虚引用访问其对应的对象。软引用和弱引用在其对象被回收之后,这些引用会被添加到引用队列中去;而虚引用在其对象被回收之前,虚引用就被添加到引用队列中去了。因此虚引用可以在其对象被释放之前进行一些操作。虚引用和引用队列绑定的方法:
//创建引用队列 ReferenceQueue<String> queue = new ReferenceQueue<String>(); //创建虚引用,并绑定引用队列 PhantomReference<String> str = new PhantomReference<String>("啦啦啦",queue);
Garbage Collection Android中的垃圾回收
Android系统会在适当的时机触发GC操作,一旦进行GC操作,就会将一些不再使用的对象进行回收。执行GC操作的时候,所有线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了Memory Leaks内存泄漏
内存泄漏表示的是不再用到的对象因为被错误引用而无法进行回收。发生内存泄漏会导致Memory Generation中的剩余可用Heap Size越来越小,这样会导致频繁触发GC,更进一步引起性能问题。总结起来其实很简单:存在无效的引用!内存泄露可以引发很多的问题,常见的内存泄露导致问题如下:应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC);应用被从后台进程干为空进程;应用莫名的崩溃(也就是超过了HeepSize阈值引起OOM);内存泄漏分析工具
看到这些问题,突然发现好像离真相越来越近了0.0。想要更加清楚地实时知晓当前应用程序的内存使用情况,我们需要通过一些工具来实现。比较好用的工具有两种:Memory Analyzer ToolLeakCanary下面我们分开介绍。Memory Analyzer Tool
Memory Analysis Tools(点我下载)是一个专门分析Java堆数据内存引用的工具,我们可以使用它方便的定位内存泄露原因,核心任务就是找到GCROOT位置。接下来说下使用步骤。抓取内存信息
AndriodStudio中抓取内存信息还是很方便的,有两种方法:使用Android Device Monitor点击Android Studio工具栏上的Tool–>Android Device Monitor使用MAT工具查看分析
这里我写了个简单的demo来测试,这个demo一共有两个页面,在跳转到第二个页面之后,新开一个现成去打印activity信息。/*** 打印ActivityName*/public void printActivityName() { for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { while (true) try { Thread.sleep(1000 * 30); Log.e(ActivityHelper.class.getSimpleName(), ((Activity) mContext).getClass().getSimpleName()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }}多次进入SecondActivity之后会发现内存一直在增长,并没有降低。而且log里会不停的输出log,打印当前activity的name。
LeakCanary
leakcanary是一个开源项目,一个内存泄露自动检测工具,是著名的GitHub开源组织Square贡献的,它的主要优势就在于自动化过早的发觉内存泄露、配置简单、抓取贴心,缺点在于还存在一些bug,不过正常使用百分之九十情况是OK的,其核心原理与MAT工具类似。因为配置十分简单,这里就不多说了。我们看下分析结果常见内存泄漏情况
构造Adapter时,没有使用缓存的 convertViewBitmap对象不在使用时调用recycle()释放内存Context使用不当造成内存泄露:不要对一个Activity Context保持长生命周期的引用。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换。非静态内部类的静态实例容易造成内存泄漏:即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中中调运thread.getLooper().quit();才不会泄露。对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。不要在执行频率很高的方法或者循环中创建对象(比如onmeasure),可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放。避免代码设计模式的错误造成内存泄露;譬如循环引用,A持有B,B持有C,C持有A,这样的设计谁都得不到释放。相关文章推荐
- 深入理解PHP7内核之FAST_ZPP
- Gson.toJson()时内存溢出StackOverflowError
- Flex 性能优化常用手法总结
- oracle 性能优化建议小结
- Lua性能优化技巧(一):前言
- Lua性能优化技巧(五):削减、重用和回收
- Lua性能优化技巧(三):关于表
- Lua性能优化技巧(四):关于字符串
- SQL Server 2016 查询存储性能优化小结
- MySQL性能优化 出题业务SQL优化
- PowerShell脚本性能优化技巧总结
- ASP在ACCESS中模糊查询"内存溢出"的解决方法
- SQL SERVER性能优化综述(很好的总结,不要错过哦)第1/3页
- MySQL Index Condition Pushdown(ICP)性能优化方法实例
- Ajax无刷新分页的性能优化方法
- c语言内存泄露示例解析
- dedecms负载性能优化实例,三招让你的dedecms快10倍以上第1/2页
- 如何减少浏览器的reflow和repaint
- javascript日期处理函数,性能优化批处理
- 解决js函数闭包内存泄露问题的办法