Android之内存泄漏与内存溢出
概览
内存泄漏(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。通常少次数的内存无法及时回收并不会到程序造成什么影响,但是如果在内存本身就比较少获取多次导致内存无法正常回收时,就会导致内存不够用,最终导致内存溢出。
内存溢出 (out of memory):是指程序申请内存时,没有足够的内存供申请者使用,导致数据无法正常存储到内存中。也就是说给你个int类型的存储数据大小的空间,但是却存储一个long类型的数据,这样就会导致内存溢出。
内存溢出和内存泄露的关系以及区别
关系:内存泄露最终会导致内存溢出,由于系统中的内存是有限的,如果过度占用资源而不及时释放,最后会导致内存不足,从而无法给所需要存储的数据提供足够的内存,从而导致内存溢出。导致内存溢出也可能是由于在给数据分配大小时没有根据实际要求分配,最后导致分配的内存无法满足数据的需求,从而导致内存溢出。文章来源:https://www.toymoban.com/news/detail-536653.html
区别:内存泄露是由于GC无法及时或者无法识别可以回收的数据进行及时的回收,导致内存的浪费;内存溢出是由于数据所需要的内存无法得到满足,导致数据无法正常存储到内存中。内存泄露的多次表现就是会导致内存溢出。文章来源地址https://www.toymoban.com/news/detail-536653.html
内存泄漏
- 从有一组定义为gc root的根节点到目标对象的路径,称为可达性。此类对象也就是存活对象,不可达的对象就是应该被gc垃圾回收机制进行回收的对象。在当前应用的生命周期内不再使用的对象,依然被gc root引用,导致无法回收,既造成了内存泄漏。
- 内存泄漏即 ML (Memory Leak) 指 程序在申请内存后,当该内存不需再使用;但 却无法被释放&归还给程序的现象。
内存泄漏带来的危害
- 用户对单次的内存泄漏并没有什么感知,但当可用的空闲空间越来越少,GC就会更容易被触发,GC进行时会停止其他线程的工作,因此有可能会造成界面卡顿等情况。
- 后续需要分配内存的时候,很容易导致内存空间不足而出现 OOM(内存溢出)。
常见的内存泄漏问题
-
- 资源性对象未关闭
- 对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。
- 例如:
- 文件I/O流、数据库连接(SQLiteDatabase)、媒体资源(MediaPlayer)、网络连接(HttpURLConnection)、Cursor对象、ContentResolver、蓝牙连接(BluetoothSocket)、网络套接字(Socket)、Bitmap对象未关闭。
- TimerTask定时任务、Timer计时器未取消。
- WebView未销毁。
- 线程池未关闭。
- 以上所提及的资源应该在使用完或者在Activity页面销毁时及时关闭。
- 资源性对象未关闭
-
- 注册对象未注销各种Listener
- 订阅者模式中,如果注册对象不再使用时,未及时注销,会导致订阅者列表中维持这对象的引用,阻止垃圾回收,导致内存泄露。
- 例如:
- BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。
- 注册对象未注销各种Listener
-
- 类的静态变量持有大数据对象
- 尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。
- 类的静态变量持有大数据对象
-
- 单例造成的内存泄漏
- 优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。
-
public class Singleton { private static Singleton instance; private Context mContext; private Singleton(Context context){ this.mContext = context; } /** * 如果传入的context是activity,service的上下文,会导致内存泄漏 * 原因是我们的instance是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长 * 当activity销毁的时候,我们的这个instance仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏 */ public static Singleton getInstance(Context context){ if (instance == null) { synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(context); } } } return instance; } }
- 单例造成的内存泄漏
-
- 非静态内部类的静态实例
- 非静态内部类持有外部类实例的引用,若非静态内部类的实例是静态的,便拥有app存活期整个生命周期,长期持有外部类的引用,阻止外部类实例被回收。
- 使用内部类的情况十分常见,尤其是匿名内部类:一些接口的匿名实现类,都是内部类。
- 解决方案:
- (1)改为静态内部类,不再持有外部类实例的引用。
- (2)避免申明非静态内部类的静态实例。
- (3)将内部类抽取出来封装成一个单例,如果需要Context,没有特殊要求就使用Application Context;如果需要Activity Context,则使用完毕置空,或者使用弱引用。
- 非静态内部类的静态实例
-
- Handler造成的泄漏
- Handler造成内存泄露的原因:非静态内部类或者匿名内部类使得Handler默认持有外部类的引用。在Activity销毁时,由于Handler可能有未执行完/正在执行的Message,导致Handler持有Activity的引用,进而导致GC无法回收Activity。
- 解决办法:
- 静态内部类+弱引用:
-
private static class MyHandler extends Handler{ private final WeakReference<MineActivity> mMineActivityWeak; public MyHandler(MineActivity mineActivity){ mMineActivityWeak = new WeakReference<>(mineActivity); } @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); MineActivity mineActivity = mMineActivityWeak.get(); if(mineActivity != null){ mineActivity.number = 5; } } }
-
- Activity销毁时,清空Handler中,未执行或正在执行的Callback以及Message。
-
// 清空消息队列,移除对外部类的引用 @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
-
- 静态内部类+弱引用:
- 注:AsyncTask内部也是Handler机制,同样存在内存泄漏风险,但其一般是临时性的。
- 解决办法:
- ①静态内部类+弱引用。
- ②在 Activity 或 Fragment 的 onDestroy() 方法中,手动取消 AsyncTask。
-
@Override protected void onDestroy() { super.onDestroy(); staticAsyncTask.cancel(true); }
-
- ③使用 AsyncTaskLoader,它是一个专门设计用来避免内存泄露的异步任务处理器。它会在 Activity 或 Fragment 销毁时自动取消所有的任务,从而避免内存泄露。
- 解决办法:
- Handler造成的泄漏
-
- 集合类持有过多对象导致的泄漏
- 集合类持有过多对象可能导致内存泄漏的原因是因为集合中的对象如果变成无用状态,但是由于被集合所持有,其内存不会及时被回收,导致内存泄漏。
- 常见的容器类还有线程池、对象池、图片缓存池等。
- 例如:
- 如果一个 Activity 中持有了一个集合对象,如果该 Activity 被销毁了但是集合中的对象没有移除或者清空,那么这些对象将无法被回收,导致内存泄漏。
- 解决办法:
- 在集合中存储对象时,要注意及时移除或者清空集合中不再需要的对象,尽量减少无用对象在集合中存储的时间。最简单的方法是:清空集合对象并设置为null。
-
@Override protected void onDestroy() { mList.clear(); mList = null; super.onDestroy(); }
-
- 使用弱引用的集合,将集合中的对象与应用程序的生命周期解耦,避免持有过多无用对象导致的内存泄漏问题。
- 在集合中存储对象时,要注意及时移除或者清空集合中不再需要的对象,尽量减少无用对象在集合中存储的时间。最简单的方法是:清空集合对象并设置为null。
- 集合类持有过多对象导致的泄漏
-
- WebView导致的泄漏
- 目前Android中WebView的实现存在很大的兼容性问题,Google支持各个ROM厂商自行定制自己的WebView实现,各个ROM间差异较大,且大多都存在内存泄露问题。除了调用其内部的clearCache()、clearHistory()、removeAllViews()、freeMemory()、destroy()和置null以外,一般比较粗暴有效的解决方法是:将包含WebView的Activity放在一个单独的进程中,不需要时将进程销毁,从而释放所有所占内存。
- 解决办法:
- 不在 xml 中定义 Webview ,这样会引用 Activity,而是在需要的时候在 Activity 中创建,并且使用 getApplicationgContext()。
-
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); mWebView = new WebView(getApplicationContext()); mWebView.setLayoutParams(params); mLayout.addView(mWebView);
-
- Activity 关闭时需要手动释放 Webview 内存。
-
override fun onDestroy() { // webview?.loadDataWithBaseURL(null, "", "text/html", "utf-8", null) // webview?.clearView() webview?.loadUrl("about:blank") webview?.parent?.let { (it as ViewGroup).removeView(webview) } webview?.stopLoading() webview?.settings?.javaScriptEnabled = false webview?.clearHistory() webview?.clearCache(true) webview?.removeAllViewsInLayout() webview?.removeAllViews() webview?.webViewClient = null webview?.webChromeClient = null webview?.destroy() webview = null super.onDestroy() }
-
- 不在 xml 中定义 Webview ,这样会引用 Activity,而是在需要的时候在 Activity 中创建,并且使用 getApplicationgContext()。
- WebView导致的泄漏
-
- 使用ListView时造成的内存泄漏
- 使用ListView时,如果不正确地使用Adapter,可能会导致内存泄漏。这是因为ListView的机制,它会在滚动过程中重用convertView,如果在convertView中持有了一些对象的引用并没有及时释放,就会导致内存泄漏。
-
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_layout, null); holder = new ViewHolder(); holder.titleTextView = convertView.findViewById(R.id.title_text_view); holder.contentTextView = convertView.findViewById(R.id.content_text_view); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } return convertView; } private static class ViewHolder { TextView titleTextView; TextView contentTextView; }
-
- 使用ListView时,如果不正确地使用Adapter,可能会导致内存泄漏。这是因为ListView的机制,它会在滚动过程中重用convertView,如果在convertView中持有了一些对象的引用并没有及时释放,就会导致内存泄漏。
- 使用ListView时造成的内存泄漏
-
- 使用第三库传递context
- 在项目中经常会使用各种三方库,有些三方库的初始化需要我们传入一个 Context 对象,尽量使用 Context.getApplicationContext,不要直接将 Activity 传递给其他组件。
- 比如:在一些广告的SDK中,它可能要求要用Activity的Context,但是还是要尽量查阅资料或者咨询一下看能不能使用Application的Context。
- 使用第三库传递context
-
- 静态View导致内存泄露
- 有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。
- 解决办法:
- 在使用静态View时,需要确保在资源回收时,将静态View detach掉。
- 静态View导致内存泄露
-
- 属性动画未及时关闭导致内存泄露
- 在使用ValueAnimator或者ObjectAnimator时,如果没有及时做cancel取消动画,就可能造成内存泄露。 因为在cancel方法里,最后调用了endAnimation(); ,在endAnimation里,有个AnimationHandler的单例,会持有属性动画对象的引用
- 解决办法:
- 在在onDestory时,调用动画的cancel方法
- 属性动画未及时关闭导致内存泄露
内存泄漏检测
- 内存泄漏检测神器LeakCanary。
- LeakCanary 是 Square 公司的一个开源库。通过它可以在 App 运行过程中检测内存泄漏,当内存泄漏发生时会生成发生泄漏对象的引用链,并通知程序开发人员。
- 原理:LeakCanary 是通过在 Application 的 registerActivityLifecycleCallbacks 方法实现对 Activity 销毁监听的,该方法主要用来统一管理所有 Activity 的生命周期。所有 Activity 在销毁时在其 OnDestory 方法中都会回调 ActivityLifecycleCallbacks 的 onActivityDestroyed 方法,而 LeakCanary 要做的就是在该方法中调用 RefWatcher.watch 方法实现对 Activity 进行内存泄漏监控。
- 接入:
- 添加依赖:
-
dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' }
-
- 添加依赖:
- 2.0以上版本会自己启动。
到了这里,关于Android之内存泄漏与内存溢出的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!