内存泄漏,简单而言就是该被释放的对象没有被释放掉,一直被某个或某些实例引用,导致无法被GC回收。
Java的内存分配策略
Java程序运行时,内存分配策略有三种:静态分配、栈分配、堆分配
。所对应的内存空间即为:静态存储区(方法区)、栈区、堆区
。
-
静态储存区:编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量;
-
栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存;
-
堆区:通常存放 new 出来的对象。由 Java 垃圾回收器回收。
局部变量的基本数据和引用存储在栈中,引用的实体存储在堆中。
因为他们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储在堆中(包括基本数据、引用和引用的实体对象)。
因为他们属于类,类对象被new的时候,会存储在堆中。
Java中如何管理内存
- 内存分配:程序控制,new 对象申请内存空间(基本类型除外),所有对象都在堆中分配空间。
- 垃圾回收:GC完成内存的释放。
Java使用有向图的方式进行内存管理,可以消除引用循环的问题。如果某个对象与这个根顶点不可达,那么可以认为这个(这些)对象不再被引用,可以被 GC 回收。
还有一个常见的内存管理技术就是使用引用计数器。虽然执行效率高,但是精度低(很难处理循环引用问题。)
Java中的内存泄漏
- 这些对象是可达的。
- 这些对象是无用的。
- 即因为可达而不会被GC回收,但是又无用占据了内存,因此造成了内存泄漏。
Android中常见的内存泄漏
集合中对象没清理造成的内存泄漏
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
所以要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
解决方案:
- 在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。
单例类造成的内存泄漏
单例的静态特性导致其生命周期同应用一样长。
解决方法:
- 将该属性的引用方式改为弱引用;
- 如果传入Context,使用ApplicationContext;
/* 例1:在activity中使用该单例时会传入context,这会延长Context的存活时间。
* 如果传入的是activity context,当activity销毁时,
* 该activity不会被GC回收,从而导致了内存泄漏。
*/
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;
}
}
// **************************
/* 解决方法
* 1. 使用Applicaion Context 代替 Activity Context
* 2. 在调用的地方使用弱引用
*/
private AppManager(Context context) {
this.context = context.getAppcalition();
}
// 或
WeakReference<MainActivity> activityReference = new WeakReference<MainActivity>(this);
Context context = activityReference.get();
if(context != null){
AppManager.getInstance(context);
// ...
}
/* 例2:在调用该单例时,点击view会传入view。
* 当该view被销毁时,由于静态的单例持有view的引用而GC无法回收,从而导致了内存泄漏。
*/
private static ScrollHelper mInstance;
private ScrollHelper() {
}
public static ScrollHelper getInstance() {
if (mInstance == null) {
synchronized (ScrollHelper.class) {
if (mInstance == null) {
mInstance = new ScrollHelper();
}
}
}
return mInstance;
}
/**
* 被点击的view
*/
private View mScrolledView = null;
public void setScrolledView(View scrolledView) {
mScrolledView = scrolledView;
}
/* 解决方法
* 使用弱引用
*/
private WeakReference<View> mScrolledViewWeakRef = null;
public void setScrolledView(View scrolledView) {
mScrolledViewWeakRef = new WeakReference<View>(scrolledView);
}
非静态或匿名内部类造成的内存泄漏
在Java中,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类,但是,静态内部类却不会。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。
解决方案:
- 将内部类变成静态内部类;
- 如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;
- 在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;
/* 由于非静态内部类或匿名内部类都会拥有所在外部类的引用。
* 当 Activity 销毁后,该匿名内部类还在执行任务,
* 导致外部的 Activity 不能被回收,导致内存泄露。
*/
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<MainActivity> activityReference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
activityReference = new WeakReference<MainActivity>(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();
}
}
/* 解决方法:
* 使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。
*/
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<MainActivity> mActivityReference;
public MyHandler(MainActivity activity) {
mActivityReference = new WeakReference<MainActivity>(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时,调用反注册;
/* 例1
* Context.getSystemService获取服务会让服务持有Activity 的引用
*/
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销毁时及时关闭或者注销。
- 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
参考:
网友评论