单例持有对象 造成泄露
单例的静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
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;
}
}
这里传入的这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长
传入的是Activity的Context:当Activity被销毁的时候,而单例还持有该对象,不能正常被系统回收,导致泄露
Handler 造成内存泄露
如handler.postDeslayed 或者还有其他未完成的任务在执行,handler会一直存活,包含它的 MainActivity 就跟着活,而这个时候MainActivity 被销毁了,导致不能及时被gc回收,发生内存泄露
public class MainActivity extends Activity {
Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
}
}
相比非静态内部类,最好使用静态内部类 Handler,静态内部类不依赖所属类,他们拥有不同的生命周期,并且使用弱引用WeakReference,垃圾回收器在回收的时候,是会忽视掉弱引用的,所以包含它的 Activity 会被正常清理掉,并且在销毁的时候清空Handler里面的消息
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
// 做相应逻辑
}
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
Activity造成泄漏
Activity 泄漏通常是内存泄漏的一种。如果持有一个未使用的 Activity 的引用,其实也就持有了 Activity 的布局,自然也就包含了所有的 View。持有静态引用需要特别注意。Activity 和 Fragment 都有自己的生命周期。一旦我们持有了静态引用,Activity 和 Fragment 就不会被垃圾回收器清理掉了。
避免 Activity 泄漏
移除掉所有的静态引用。
考虑用 EventBus 来解耦 Listener。
记着在不需要的时候,解除 Listener 的绑定。
尽量用静态内部类。
做 Code Review, 能很早的发现内存泄漏。
了解你程序的结构。
用类似 MAT,Eclipse Analyzer,LeakCanary 这样的工具分析内存。
在 Callback 里打印 Log。
线程 造成内存泄漏
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成,那么将导致Activity的内存资源无法回收,造成内存泄漏
应该使用静态内部类来避免这些问题.
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
资源未关闭 造成内存泄漏
对于使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
集合中的对象未清理 造成内存泄露
这个比较好理解,如果一个对象放入到ArrayList、HashMap等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove,或者clear集合,以避免内存泄漏。
资源未关闭或释放导致内存泄露
在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。
属性动画 造成内存泄露
动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}
WebView 造成内存泄露
关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。
@Override
protected void onDestroy() {
super.onDestroy();
//先从父控件中移除
WebView mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
Java中的 4 种引用
在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(StrongReference)、软引用(SoftReference)、弱引用(Weak Reference)、虚引用(PhantomReference)4种,这4种引用强度依次逐渐减弱。
强引用
强引用就是指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用被引用,还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用
软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。用SoftReference类来实现软引用。
弱引用
弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。用WeakReference类来实现弱引用。
虚引用
虚引用也称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。用PhantomReference类来实现虚引用。
网友评论