Android内存泄漏分析与总结
为了书写⽅便内存泄漏的实例,本⽂⽤leakcanary检测内存泄漏。
Java中的内存分配
在了解内存泄漏之前,先简单了解⼀下java的内存分配,主要分为⼀下三块:
静态储存区:编译时就分配好,在程序整个运⾏期间都存在。它主要存放静态数据和常量。
栈区:当⽅法执⾏时,会在栈区内存中创建⽅法体内部的局部变量,⽅法结束后⾃动释放内存。
堆区:通常存放new出来的对象,由GC负责回收
Java中四种引⽤类型
强引⽤
在⽇常的编程中,⽤的最多的强引⽤如下:
User user = new User();
对于强引⽤的回收,JVM是不会让GC去主动回收具有强引⽤的对象,⽽我们需要回收强引⽤对象的时候,常常通过置空,即object=null。这样GC就会去回收该强引⽤对象
软引⽤
软引⽤的创建
SoftReference<String> softRef = new SoftReference<String>();
当⼀个对象只具有软引⽤的时候,内存空间⾜够的话,GC不会回收,⽽当内存空间不⾜的时候,GC就会回收这些对象。在Android2.3之后,GC会很频繁的操作,因此在⽤软引⽤的时候要注意选择不常⽤的对象作为软引⽤,否则会降低效率。
弱引⽤
弱引⽤的创建
WeakReference<String> softRef = new WeakReference<String>();
当⼀个对象只具有软引⽤的时候,内存空间⾜够的话,GC发现即回收。当然,由于GC的线程优先级很低,因此弱引⽤对象不⼀定会被及时的回收。
虚引⽤
虚引⽤必须和引⽤队列(ReferenceQueue)联合使⽤。当垃圾回收器准备回收⼀个对象时,如果发现它还有虚引⽤,就会在回收对象的内存之前,把这个虚引⽤加⼊到与之关联的引⽤队列中。程序可以通过判断引⽤队列中是否存在该对象的虚引⽤,来了解这个对象是否将要被回收。
内存泄漏定义
内存泄漏也称作“存储渗漏”,⽤动态存储分配函数动态开辟的空间,在使⽤完毕后未释放,结果导致⼀直占据该内存单元。直到程序结束。(其实说⽩了就是该内存空间使⽤完毕之后未回收)即所谓内存泄漏(摘⾃百度百科)。在android⾥⾯引起内存泄漏指的是对象的⽣命周期结束,⽽该对象依然被其他对象所持有,导致该对象所占内存⽆法释放。
内存泄漏带来的影响
在android⾥⾯,出现内存泄漏会导致系统为应⽤分配的内存会不断减少,从⽽造成app在运⾏时会出现卡断(内存占⽤⾼时JVM虚拟机会频繁触发GC),影响⽤户体验。同时,可能会引起OOM(内存溢出),从⽽导致应⽤程序崩溃!
Android中常见导致内存泄漏操作
传统的内存泄漏是由于分配的内存未能及时释放导致的。当⼀个对象不再被使⽤时,该对象仍然存在强引⽤的话(User user=new User(),其他对象引⽤这个user实例的时候就是⼀个强引⽤),垃圾回收器就⽆法对其进⾏垃圾回收,便会造成内存泄漏。在android⾥⾯,⽇常所见的泄漏基本出现在context的引⽤上,尤其activity对象,它会引⽤⼤量占⽤内存的对象,服务,资源,view等。下⾯举例说明⼋种常见的可能导致activity泄漏的情况。
集合类泄漏
如果某个集合是全局性的变量(⽐如 static 修饰),集合内直接存放⼀些占⽤⼤量内存的对象(⽽不是通过弱引⽤存放),那么随着集合size 的增⼤,会导致内存占⽤不断上升,⽽在 Activity 等销毁时,集合中的这些对象⽆法被回收,导致内存泄露。⽐如我们喜欢通过静态HashMap 做⼀些缓存之类的事,这种情况要⼩⼼,集合内对象建议采⽤弱引⽤的⽅式存取,并考虑在不需要的时候⼿动释放。
单例模式(静态activity的引⽤)
单例模式造成的内存泄漏可能在你的项⽬中存在,只是我们平常没怎么关注,并不是所有的内存泄漏都会引起卡顿或者闪退,造成内存泄漏的写法如下
public class RequestImpl {
private Context context;
private static RequestImpl mInstance;
public static RequestImpl getInstance(Context context) {
if (mInstance == null) {
mInstance = new RequestImpl(context);
}
return mInstance;
}
private RequestImpl(Context context) {
}
}
当我们在调⽤getInstance的时候不经意的传⼊了⼀个activity的this,⽽不是传⼊appication的context时候就会造成内存泄漏如下
解决⽅法就是在调⽤getInstance的时候传⼊application的context,或者在RequestImpl的构造函数⾥⾯调⽤
⾮静态内部类
⾮静态内部类造成的内存溢出常出Handler,Thread,Timer Task(定时任务),这些耗时操作如果在activity⽣命周期结束后还在运⾏的话,可能会造成内存溢出。如下Thread写法
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
testInnner();
}
private void testInnner() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
recycle是什么意思}
}
内存泄漏如下,内部类Thread引⽤了Mainctivity对象
下⾯再列举⼀个⾮常常见的handler可能造成的内存泄漏
通过字⾯翻译android系统给出的提⽰这个handler类应该是static的,否则可能引起内存泄漏,⽽ctrl+F1查看到的解决建议如下
since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
翻译下来的意思就是,内部类声明在外部类从⽽阻⽌了GC的回收,如果该handler实例的轮询器或者消息队列是在⾮主线程创建的,那这样创建handler实例是不存在什么问题,如果looper或者messagequeue创建在主线程,我们需要将handler声明为静态内部类,同时⽤WeakRefenrce与引⽤外部对象(弱引⽤)。因此有如下的handler解决⽅案
MyHandler mHandler = new MyHandler(this);
static class MyHandler extends Handler {
WeakReference<MainActivity> activityReference;
MyHandler(MainActivity activity) {
activityReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = ();
//判断是因为GC是对弱引⽤进⾏回收的
if (activity != null) {
}
}
}
对于以上现象出现,将testInner⽅法改为静态⽅法即可。类似的TimerTask定时任务也会出现此种情况.当书写内部类的时候要确保⾃⼰的耗时操作在activity结束后没有引⽤activity对象
getSystemServie
AudioManager  mAudioManager= (AudioManager) SystemService(Context.AUDIO_SERVICE);
SensorManager mSensorManager =(SensorManager) SystemService(Context.SENSOR_SERVICE);
类似以上SystemService()是创建了⼀个服务,在activity⽣命周期结束后,⼀定要及时释放。调⽤unregisterListener⽅法。资源未关闭造成的内存泄漏
BroadcastReceiver,ContentObserver 之类的没有解除注册啊;Cursor,Stream 之类的没有 close 啊;⽆限循环的动画在 Activity 退出前没有停⽌啊;⼀些其他的该 释放或者回收的没有被操作,⽐如⾃定义view的属性获取类 TypedArray需要调⽤recycle
内存泄漏的检测与分析
因为项⽬中是在⽤Android Studio进⾏开发,且最近升级到了2.2的版本,因此主要利⽤Android Studio来进⾏分析。⽽且AS⽬前已经完善了这些应⽤的性能分析功能。个⼈认为没必要再去⽤Eclipse的MAT插件来分析。
利⽤内存泄漏检测开源项⽬LeakCanary
注意leakcanary要应⽤的⽂件读写权限,因此注意适配android6.0的权限动态申请。 leakcanary的使⽤相当的⽅便,先通过gradle配置leakcanary库。然后在应⽤程序的Application中调⽤如下⽅法即可:
LeakCanary.install(this);
从上往下看就可以看到具体是哪部分对象的引⽤造成的内存泄漏。
同时,我们可以将产⽣的.hropf⽂件⽤Android Studio打开(直接拖拽进AS即可)。
在左上⾓划横线的地⽅选择树结构,可以很快的定位到应⽤程序的包,然后选中包(如果你想单独定位某⼀个类,就选中该类),再展开Anlyzer Tasks,然后点击右上⾓的绿⾊三⾓箭头,启动分析。
这两个选项都是默认选择,查存在内存泄漏的activity以及定义的字符串存在重复定义的地⽅。点击run后,运⾏结果如下图所⽰: