天天看点

android final内存泄漏,Android内存泄漏

内存泄漏,简单而言就是该被释放的对象没有被释放掉,一直被某个或某些实例引用,导致无法被GC回收。

Java的内存分配策略

Java程序运行时,内存分配策略有三种:静态分配、栈分配、堆分配。所对应的内存空间即为:静态存储区(方法区)、栈区、堆区。

静态储存区:编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量;

栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存;

堆区:通常存放 new 出来的对象。由 Java 垃圾回收器回收。

局部变量的基本数据和引用存储在栈中,引用的实体存储在堆中。

因为他们属于方法中的变量,生命周期随方法而结束。

成员变量全部存储在堆中(包括基本数据、引用和引用的实体对象)。

因为他们属于类,类对象被new的时候,会存储在堆中。

Java中如何管理内存

内存分配:程序控制,new 对象申请内存空间(基本类型除外),所有对象都在堆中分配空间。

垃圾回收:GC完成内存的释放。

android final内存泄漏,Android内存泄漏

Java内存管理

Java使用有向图的方式进行内存管理,可以消除引用循环的问题。如果某个对象与这个根顶点不可达,那么可以认为这个(这些)对象不再被引用,可以被 GC 回收。

还有一个常见的内存管理技术就是使用引用计数器。虽然执行效率高,但是精度低(很难处理循环引用问题。)

Java中的内存泄漏

这些对象是可达的。

这些对象是无用的。

即因为可达而不会被GC回收,但是又无用占据了内存,因此造成了内存泄漏。

android final内存泄漏,Android内存泄漏

Java中的内存泄漏

Android中常见的内存泄漏

集合中对象没清理造成的内存泄漏

我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

所以要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

解决方案:

在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。

单例类造成的内存泄漏

单例的静态特性导致其生命周期同应用一样长。

解决方法:

将该属性的引用方式改为弱引用;

如果传入Context,使用ApplicationContext;

public class AppManager {

private static AppManager instance;

private Context context;

private AppManager(Context context) {

this.context = context;

}

public static AppManager getInstance(Context context) {

if (instance == null) {

instance = new AppManager(context);

}

return instance;

}

}

// **************************

private AppManager(Context context) {

this.context = context.getAppcalition();

}

// 或

WeakReference activityReference = new WeakReference(this);

Context context = activityReference.get();

if(context != null){

AppManager.getInstance(context);

// ...

}

private static ScrollHelper mInstance;

private ScrollHelper() {

}

public static ScrollHelper getInstance() {

if (mInstance == null) {

synchronized (ScrollHelper.class) {

if (mInstance == null) {

mInstance = new ScrollHelper();

}

}

}

return mInstance;

}

private View mScrolledView = null;

public void setScrolledView(View scrolledView) {

mScrolledView = scrolledView;

}

private WeakReference mScrolledViewWeakRef = null;

public void setScrolledView(View scrolledView) {

mScrolledViewWeakRef = new WeakReference(scrolledView);

}

非静态或匿名内部类造成的内存泄漏

在Java中,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类,但是,静态内部类却不会。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。

解决方案:

将内部类变成静态内部类;

如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;

在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;

public class LeakAct extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.aty_leak);

test();

}

//这儿发生泄漏

public void test() {

new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

}

//加上static,变成静态匿名内部类

public static void test() {

new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

// 如果是内部类,静态化内部类

static class MyThread extends Thread {

@Override

public void run() {

// ...

}

}

静态内部类持有外部引用造成的内存泄漏

虽然静态内部类的生命周期和外部类无关,但是如果在内部类中想要引入外部成员变量的话,这个成员变量必须是静态的了,这也可能导致内存泄露。

解决方法: 使用弱引用。

public class MainActivity extends Activity {

private static WeakReference activityReference;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

activityReference = new WeakReference(this);;

}

private static class MyThread extends Thread {

@Override

public void run() {

// 耗时操作

MainActivity activity = activityReference.get();

if(activity != null){

activity...

}

}

}

}

Handle造成的内存泄漏

当Handler中有延迟的的任务或是等待执行的任务队列过长,由于消息持有对Handler的引用,而Handler又持有对其外部类的潜在引用,这条引用关系会一直保持到消息得到处理,而导致了Activity无法被垃圾回收器回收,而导致了内存泄露。

解决方案:

可以把Handler类放在单独的类文件中,或者使用静态内部类便可以避免泄露;

如果想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity。使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

// ...

}

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.

mLeakyHandler.postDelayed(new Runnable() {

@Override

public void run() { }

}, 1000 * 60 * 10);

// Go back to the previous Activity.

finish();

}

}

public class MainActivity extends Activity {

private final MyHandler mHandler = new MyHandler(this);

private static final Runnable mRunnable = new Runnable() {

@Override

public void run() { }

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mHandler.postDelayed(mRunnable, 1000 * 60 * 10);

finish();

}

private static class MyHandler extends Handler {

private final WeakReference mActivityReference;

public MyHandler(MainActivity activity) {

mActivityReference = new WeakReference(activity);

}

@Override

public void handleMessage(Message msg) {

MainActivity activity = mActivityReference.get();

if (activity != null) {

// ...

}

}

}

}

static 修饰activity或间接修饰activity context

由于静态变量和应用存活时间是相同的,直接或间接修饰了activity很荣耀造成内存泄漏问题。

解决方案:

不使用static修饰

在适当的地方将其置空

弱引用

Activity Context 的不正确使用造成的内存泄漏

在Android应用程序中通常可以使用两种Context对象:Activity和Application。当类或方法需要Context对象的时候常见的做法是使用第一个作为Context参数。这样就意味着View对象对整个Activity保持引用,因此也就保持对Activty的所有的引用。

解决方案:

使用Application Context代替Activity Context,因为Application Context会随着应用程序的存在而存在,而不依赖于activity的生命周期;

对Context的引用不要超过它本身的生命周期,慎重的对Context使用“static”关键字。Context里如果有线程,一定要在onDestroy()里及时停掉。

注册监听器造成的内存泄漏

系统服务可以通过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有Activity 的引用,如果在Activity onDestory时没有释放掉引用就会内存泄漏。

解决方案:

使用ApplicationContext代替ActivityContext;

在Activity执行onDestory时,调用反注册;

mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);

// 解决

mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);

资源性对象造成的内存泄漏

Cursor,Stream没有close,View没有recyle都有可能造成内存泄漏。

在不使用他们时,应该及时关闭他们,以便它们的缓冲及时回收内存。

如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。

虽然系统在回收它时也会关闭它,但是这样的效率太低了。

总结

对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。

尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。

对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:

将内部类改为静态内部类

静态内部类中使用弱引用来引用外部类的成员变量

Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.

在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。

正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。

保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

参考: