Android内存泄漏分析及调试

尊重原创作者,转载请注明出处:

此文承接我的另一篇文章:Android进程的内存管理分析

首先了解一下dalvik的Garbage Collection:

如上图所示,GC会选择一些它了解还存活的对象作为内存遍历的根节点(GC Roots),比方说thread stack中的变量,JNI中的全局变量,zygote中的对象(class loader加载)等,然后开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。如下图蓝色部分。

Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。下面分析一些可能导致内存泄漏的情景。

常见的内存泄漏

1、非静态内部类的静态实例容易造成内存泄漏

[java] view plain

public class MainActivityextends Activity{static Demo sInstance = null;@Overridepublic void onCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (sInstance == null){sInstance= new Demo();}}class Demo{voiddoSomething(){System.out.print("dosth.");}}}

上面的代码中的sInstance实例类型为静态实例,在第一个MainActivity act1实例创建时,sInstance会获得并一直持有act1的引用。当MainAcitivity销毁后重建,因为sInstance持有act1的引用,所以act1是无法被GC回收的,进程中会存在2个MainActivity实例(act1和重建后的MainActivity实例),这个act1对象就是一个无用的但一直占用内存的对象,即无法回收的垃圾对象。所以,对于lauchMode不是singleInstance的Activity, 应该避免在activity里面实例化其非静态内部类的静态实例。

2、activity使用静态成员

[java] view plain

private static Drawable sBackground; @Override protected void onCreate(Bundle state) {super.onCreate(state);TextView label = new TextView(this);label.setText("Leaks are bad");if (sBackground == null) {sBackground = getDrawable(R.drawable.large_bitmap);}label.setBackgroundDrawable(sBackground);setContentView(label); }

由于用静态成员sBackground缓存了drawable对象,所以activity加载速度会加快,但是这样做是错误的。因为在android 2.3系统上,它会导致activity销毁后无法被系统回收。

label .setBackgroundDrawable函数调用会将label赋值给sBackground的成员变量mCallback。

上面代码意味着:sBackground(GC Root)会持有TextView对象,而TextView持有Activity对象。所以导致Activity对象无法被系统回收。

下面看看android4.0为了避免上述问题所做的改进。

先看看android 2.3的Drawable.Java对setCallback的实现:

public final void setCallback(Callback cb){

mCallback = cb;

}

再看看android 4.0的Drawable.Java对setCallback的实现:

public final void setCallback(Callback cb){

mCallback = newWeakReference<Callback> (cb);

}

在android 2.3中要避免内存泄漏也是可以做到的, 在activity的onDestroy时调用

sBackgroundDrawable.setCallback(null)。

以上2个例子的内存泄漏都是因为Activity的引用的生命周期超越了activity对象的生命周期。也就是常说的Context泄漏,因为activity就是context。

想要避免context相关的内存泄漏,需要注意以下几点:

·不要对activity的context长期引用(一个activity的引用的生存周期应该和activity的生命周期相同)

·如果可以的话,尽量使用关于application的context来替代和activity相关的context

·如果一个acitivity的非静态内部类的生命周期不受控制,那么避免使用它;正确的方法是使用一个静态的内部类,并且对它的外部类有一WeakReference,就像在ViewRootImpl中内部类W所做的那样。

3、使用handler时的内存问题

我们知道,Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。 所以正确处理Handler等之类的内部类,应该将自己的Handler定义为静态内部类。

HandlerThread的使用也需要注意:

当我们在activity里面创建了一个HandlerThread,代码如下:

[java] view plain

public classMainActivity extends Activity{@Overridepublic void onCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Thread mThread = newHandlerThread("demo", Process.THREAD_PRIORITY_BACKGROUND);mThread.start();MyHandler mHandler = new MyHandler( mThread.getLooper( ) );…….…….…….}@Overridepublic void onDestroy(){super.onDestroy();}}

这个代码存在泄漏问题,因为HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了activity生命周期,当横竖屏切换,,HandlerThread线程的数量会随着activity重建次数的增加而增加。

应该在onDestroy时将线程停止掉:mThread.getLooper().quit();

另外,对于不是HandlerThread的线程,也应该确保activity消耗后,线程已经终止,可以这样做:在onDestroy时调用mThread.join();

4、注册某个对象后未反注册

注册广播接收器、注册观察者等等,比如:

乐观者在灾祸中看到机会;悲观者在机会中看到灾祸

Android内存泄漏分析及调试

相关文章:

你感兴趣的文章:

标签云: