前言
内存泄露(Memory Leak)是指程序在运行过程中,由于疏忽或错误导致已分配的内存空间无法被正确释放,使得这部分内存一直被占用而无法被操作系统回收再利用的现象。
这里简单总结一下内存泄露相关知识,记录于此,方便自己查阅和回顾。
本文主要摘抄的,多谢
正文
原因
由于 Java
存在垃圾回收机制(GC
),理应不存在内存泄露;出现内存泄露的原因仅仅是外部人为原因 = 无意识地持有对象引用,使得 持有引用者的生命周期 > 被引用者的生命周期
常见的内存泄露的主要原因:
集合类
static关键字修饰的成员变量
非静态内部类、匿名类
资源对象使用后未关闭
场景示例
集合类
集合类添加元素之后,仍引用集合元素对象,导致该集合元素对象不可回收,从而导致内存泄露。
举个例子:
// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List List<Object> objectList = new ArrayList<>(); for (int i = 0; i < 10; i++) { Object o = new Object(); objectList.add(o); o = null; }
虽释放了集合元素引用的本身:o=null,但但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象。
解决方法:
// 释放objectList objectList.clear(); objectList=null;
static关键字修饰的成员变量
被
Static
关键字修饰的成员变量的生命周期 = 应用程序的生命周期
若使被 Static
关键字修饰的成员变量 引用耗费资源过多的实例(如Context
),则容易出现该成员变量的生命周期大于引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露。
举个例子:
public class ClassName { // 定义1个静态变量 private static Context mContext; //... // 引用的是Activity的context mContext = context; }
当Activity需销毁时,由于mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity无法被回收,从而出现内存泄露。
解决方法:
尽量避免
Static
成员变量引用资源耗费过多的实例(如Context
)若需引用
Context
,则尽量使用Applicaiton
的Context
使用 弱引用
(WeakReference)
代替 强引用 持有实例
非静态内部类、匿名类
若 非静态内部类所创建的实例 = 静态(其生命周期 = 应用的生命周期),会因 非静态内部类默认持有外部类的引用 而导致外部类无法释放,最终造成内存泄露。
举个例子:
// 背景: a. 在启动频繁的Activity中,为了避免重复创建相同的数据资源,会在Activity内部创建一个非静态内部类的单例 b. 每次启动Activity时都会使用该单例的数据 public class TestActivity extends AppCompatActivity { // 非静态内部类的实例的引用 // 注:设置为静态 public static InnerClass innerClass = null; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 保证非静态内部类的实例只有1个 if (innerClass == null) innerClass = new InnerClass(); } // 非静态内部类的定义 private class InnerClass { //... } } // 造成内存泄露的原因: // a. 当TestActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期 = 应用App的生命周期、持有外部类TestActivity的引用 // b. 故 TestActivity无法被GC回收,从而导致内存泄漏
解决方法:
将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)
该内部类抽取出来封装成一个单例
尽量 避免 非静态内部类所创建的实例 = 静态
若需使用
Context
,建议使用Application
的Context
除了上面还有下面两种容易出现内存泄露。
多线程(AsyncTask、实现Runnable接口、继承Thread类)内存泄露
Activity退出时,主动停止线程。
Handler 内存泄露
Activity退出时,清楚所有广播
mHandler.removeCallbacksAndMessages(null);
或者
//参考文2的代码 public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 实例化自定义的Handler类对象->>分析1 // 注: // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue; // b. 定义时需传入持有的Activity实例(弱引用) showhandler = new FHandler(this); new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息标识 msg.obj = "AA";// 消息存放 showhandler.sendMessage(msg); } }.start(); } // 设置为:静态内部类 private static class FHandler extends Handler{ // 定义 弱引用实例 private WeakReference<Activity> reference; // 在构造方法中传入需持有的Activity实例 public FHandler(Activity activity) { // 使用WeakReference弱引用持有Activity实例 reference = new WeakReference<Activity>(activity); } // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG, "收到线程1的消息"); break; case 2: Log.d(TAG, " 收到线程2的消息"); break; } } } }
资源对象使用后未关闭
对于资源的使用(如 广播BraodcastReceiver
、文件流File
、数据库游标Cursor
、图片资源Bitmap
等),若在Activity
销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏。
解决方法:
// 对于 广播BraodcastReceiver:注销注册 unregisterReceiver() // 对于 文件流File:关闭流 InputStream / OutputStream.close() // 对于数据库游标cursor:使用后关闭游标 cursor.close() // 对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null Bitmap.recycle(); Bitmap = null; // 对于动画(属性动画) // 将动画设置成无限循环播放repeatCount = “infinite”后 // 在Activity退出时记得停止动画
参考文章
《》
《
历史上的今天
- 《简单记录内存泄漏相关知识》
- 《本地惠生活》
- 《国学迷》
- 《安视迪(上海)视觉科技有限公司》
- 《全民健康网》
- 《文心智能体平台》
- 《淘宝商品网》
- 《花蝴蝶免费视频》
- 《安徽华宇电缆集团有限公司》