关于Android应用程序内存泄漏 你需要知道的一切

原文:https://blog.aritraroy.in/everything-you-need-to-know-about-memory-leaks-in-android-apps-655f191ca859

作者:Aritra Roy

垃圾收集器是你的朋友,但并非总是如此

Java内置了专用的内存管理系统,可以在适当的时候自动释放内存,那为什么我需要关注这一切。垃圾收集器有问题吗?

不,肯定不是。**垃圾收集器的工作正常,**但是我们自己的编程错误,有时会阻止垃圾收集器的正常工作。

所以是我们自己的错,导致了所有混乱。垃圾收集器是Java最好的成就之一,值得尊重。

关于垃圾收集器的更多内容

垃圾收集器如何工作的:

每个Android(或Java)应用程序都有一个起点,从对象开始实例化并调用方法。因此,我们可以将此起点视为内存树的“根”。一些对象直接引用“根”,其他对象从它们实例化,持有对这些对象的引用,依此类推形成了引用链。

因此,垃圾收集器从GC根开始并遍历直接或间接链接到根的对象。在此过程结束时,有一些对象从未被GC访问过。

这些是你的垃圾(或死亡的对象),这些是我们心爱的垃圾收集器有资格收集的垃圾。

到目前为止,这个故事看起来像是一个童话故事。现在让我们深入挖掘它,真正的乐趣开始了。

了解更多有关垃圾收集器的信息:

https://www.youtube.com/watch?v=UnaNQgzw4zY

http://www.cubrid.org/blog/dev-platform/understanding-java-garbage-collection/

什么是内存泄漏?

简单来说,当一个对象目的达到,很久之后你仍然持有此对象时,就会发生内存泄漏

每个对象都有自己的生命周期,之后需要说再见并释放内存。但是如果某些其他对象(直接或间接)持有此对象,则垃圾收集器将无法收集它。而这正是一个内存泄漏。

但好消息是,您不必过多担心应用中发生的每一次内存泄漏。并非所有内存泄漏都会损害您的应用。

有一些泄漏实际上很小(泄漏了几千字节的内存),并且Android框架本身有一些(是的,你读得正确;-)),你不能或不需要修复。这些通常会对您应用的效果产生最小影响,并且可以安全地忽略。

但是还有其他一些情况可能会让你的应用程序崩溃。这些是你需要注意的。

为什么需要关注修复内存泄漏?

没有人想使用一个缓慢,滞后,消耗大量内存或使用几分钟后崩溃的应用程序。这是一个非常糟糕的体验,如果长久以来一直如此,那么你很可能会永远失去该用户。

当用户使用您的应用程序时,堆内存也会不断增加,如果您的应用程序中存在内存泄漏,则GC无法释放堆中未使用的内存。因此,应用程序的堆内存将不断增加,直到达到临界点,从而无法为您的应用程序分配更多内存,从而导致可怕的OutOfMemoryError并最终导致应用程序崩溃。

您还必须记住垃圾收集过程会消耗性能,因此垃圾收集器运行的越少,您的应用程序性能就越好。

随着应用程序的使用和堆内存不断增加,一个简短的GC将启动并尝试清除直接死对象。现在这些短的GC并发(在一个单独的线程上),轻微减慢你的应用程序(2-5毫秒暂停)。

但是如果你的应用程序在隐藏了一些严重的内存泄漏,那么这些短的GC将无法回收内存,并且堆将继续增加,从而迫使更大的GC启动,这通常是“stop the world“GC。触发这个GC将暂停整个应用程序主线程(大约50-100毫秒),从而使您的应用程序严重卡顿,有时几乎无法使用一段时间。

如何检测这些内存泄漏?

Android Studio有一个非常有用和强大的工具,监视器(Monitors)。监视器不仅用于内存使用,还用于网络,CPU和GPU使用(这里有更多信息)。

在调试应用程序时,您应该密切关注此内存监视器。内存泄漏的第一个症状是,当您使用应用程序时,内存占用会不断增加,即使您将应用程序置于后台,内存占用也不会下降。

分配追踪器就派上用场了,你可以用它来检查应用程序的内存分配情况,查看不同类型的对象占用内存的百分比。您可以清楚地了解哪些对象占用的内存最多,需要专门解决。

您现在需要使用Dump Java Heap选项来创建heap dump,也就是内存快照。

我们的工程师往往很懒,这正是LeakCanary**拯救我们的地方。**该库与您的应用程序一起运行,在需要时转储内存,查找潜在的内存泄漏并为您提供通知,其中包含干净且有用的堆栈跟踪以查找泄漏的根本原因。

*LeakCanary使任何人都可以非常轻松地检测应用程序中的泄漏。*非常感谢Py(来自Square)开发了一个如此节约生命的库。

详细了解如何充分利用此库,请查看此内容

一些常见的内存泄漏场景以及解决方案

未取消注册的监听器

在许多情况下,您在Activity(或Fragment)中注册了一个监听器,但忘记取消注册它。如果你运气不好,这很容易导致巨大的内存泄漏。所以如果你在某个地方注册了监听,你还需要在那里取消注册。

现在让我们通过一个简单的例子来看看它。假设您想在应用程序中接收位置更新,那么您需要做的就是获取LocationManager系统服务并注册一个用于位置更新的监听器。

private void registerLocationUpdates(){ mManager = (LocationManager)getSystemService(LOCATION_SERVICE); mManager.requestLocationUpdates(LocationManager. GPS_PROVIDER , TimeUnit. MINUTES .toMillis(1), 100, this);
}

您在Activity中实现了监听器接口,*因此LocationManager保留了对它的引用。*现在当你的Activity死掉的时候,Android框架会调用它上面的onDestroy(),但垃圾收集器将无法从内存中删除实例,因为LocationManager仍然保留了对它的强引用。

**解决方案非常简单。**只需在onDestroy()方法中取消注册监听器,就可以了。这就是我们大多数人忘记或甚至不知道的事情。

内部类

内部类在Java中非常常见,并且由于其简单性而被许多Android开发人员用于各种任务。但由于使用不当,这些内部类也可能导致潜在的内存泄漏。

让我们再次借助一个简单的例子来看看:

public class BadActivity extends Activity {private TextView mMessageView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_bad_activity);mMessageView = (TextView) findViewById(R.id.messageView);new LongRunningTask().execute();}private class LongRunningTask extends AsyncTask<Void, Void, String> {@Overrideprotected String doInBackground(Void... params) {return "Am finally done!";}@Overrideprotected void onPostExecute(String result) {mMessageView.setText(result);}}
}

这是一个非常简单的Activity,它在后台线程中启动一个长时间运行的任务(可能是复杂的数据库查询或慢速网络调用)。任务完成后,结果显示在TextView中。好像一切正常?

不,当然不是。这里的问题是非静态内部类包含对外部封闭类的隐式引用(即,Activity本身)。现在,如果我们旋转屏幕或者这个LongRunningTask的任务比Activity的生命周期长,那么它不会让垃圾收集器从内存中收集整个Activity实例。一个简单的错误导致巨大的内存泄漏。

但是解决方案也非常简单:

public class GoodActivity extends Activity {private AsyncTask mLongRunningTask;private TextView mMessageView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_good_activity);mMessageView = (TextView) findViewById(R.id.messageView);mLongRunningTask = new LongRunningTask(mMessageView).execute();}@Overrideprotected void onDestroy() {super.onDestroy();mLongRunningTask.cancel(true);}private static class LongRunningTask extends AsyncTask<Void, Void, String> {private final WeakReference<TextView> messageViewReference;public LongRunningTask(TextView messageView) {this.messageViewReference = new WeakReference<>(messageView);}@Overrideprotected String doInBackground(Void... params) {String message = null;if (!isCancelled()) {message = "I am finally done!";}return message;}@Overrideprotected void onPostExecute(String result) {TextView view = messageViewReference.get();if (view != null) {view.setText(result);}}}
}

正如你所看到的,我们将非静态内部类改为静态内部类,静态内部类不包含对其封闭外部类的任何隐式引用。 但是现在我们无法从静态上下文访问外部类的非静态变量(如TextView),因此我们必须通过其构造函数将所需的对象引用传递给内部类。

强烈建议在WeakReference中包装这些对象引用以防止进一步的内存泄漏。你需要开始学习有关Java中的引用类型以及如何善用它们,以避免内存泄漏。

匿名类

匿名类是许多开发人员的最爱,因为它们的定义方式使得使用它们编写代码变得非常简单和简洁。但根据我的经验,这些匿名类是内存泄漏的最常见原因

匿名类只是非静态内部类,它可能导致潜在的内存泄漏,因为我之前谈到的原因相同。您一直在应用中的多个位置使用它,但不知道如果使用不当会严重影响您应用的性能。

public class MoviesActivity extends Activity {private TextView mNoOfMoviesThisWeek;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_movies_activity);mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();repository.getMoviesThisWeek().enqueue(new Callback<List<Movie>>() {@Overridepublic void onResponse(Call<List<Movie>> call,Response<List<Movie>> response) {int numberOfMovies = response.body().size();mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));}@Overridepublic void onFailure(Call<List<Movie>> call, Throwable t) {// Oops.}});}
}

在这里,我们使用非常流行的库Retrofit进行网络调用并在TextView中显示结果。很明显,Callable对象保持对封闭的Activity类的引用。

现在,如果此网络调用在非常慢的连接上运行,并且在调用结束之前,以某种方式旋转或销毁Activity 整个Activity实例将被泄露。

始终建议尽可能使用静态内部类而不是匿名类。并不是说我突然告诉你完全停止使用匿名类,但你必须理解并判断何时使用它们是安全的,何时不能。

位图

您在应用中看到的每个图像都只是包含图像的整个像素数据的位图对象。

现在这些位图对象通常很重,如果处理不当会导致严重的内存泄漏,最终可能会因OutOfMemoryError导致应用程序崩溃。与您在应用程序中使用的图像资源相关的位图内存全部由框架本身自动管理,但如果您手动处理位图,请确保在使用后 recycle()回收它们。

您还必须学习如何正确管理这些位图,比如通过缩小位图来加载大位图,并尽可能使用位图缓存和位图池来减少内存使用。这是一个很好的资源,可以详细了解位图处理。

上下文

内存泄漏的另一个常见原因是滥用上下文实例。Context仅仅是一个抽象类,有许多类(如Activity,Application,Service等)是其扩展,以提供他们自己的功能。

如果你想在Android中完成任务,那么Context对象就是你的最佳选择。

但这些Context之间存在差异。理解活动级上下文和应用程序级上下文之间的区别以及在什么情况下应该使用哪个上下文非常重要。

在错误的位置使用活动上下文可以保持对整个活动的引用并导致潜在的内存泄漏。这是一篇很好的文章,供您开始使用。

结论

现在您必须知道垃圾收集器的工作原理,内存泄漏是什么以及它们如何对您的应用产生重大影响。您还了解了如何检测和修复这些内存泄漏。

让我们从现在开始构建高质量,高性能的Android应用程序。检测和修复内存泄漏不仅会使您的应用程序的用户体验更好,而且会慢慢使您成为更优秀的开发人员。

本文最初发布于TechBeacon

关于Android应用程序内存泄漏 你需要知道的一切相关推荐

  1. android应用内存分析,Android应用程序内存分析-Memory Analysis for Android Applications

    Android应用程序内存分析 原文链接:http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html ...

  2. Android中的内存泄漏

    ** Android中的内存泄漏 ** Android中的内存泄漏: 概念:程序在申请内存后,当该内存不需再使用但却无法被释放 & 归还给程序的现象,对应用程序的影响,容易使得应用程序发生内存 ...

  3. 谈谈android中的内存泄漏

    写在前面 内存泄漏实际上很多时候,对于开发者来说不容易引起重视.因为相对于crash来说,android中一两个地方发生内存泄漏的时候,对于整体没有特别严重的影响.但是我想说的是,当内存泄漏多的时候, ...

  4. Linux 下几款程序内存泄漏检查工具

    Linux 下几款程序内存泄漏检查工具 chenyoubing | 发布于 2016-07-23 10:08:09 | 阅读量 93 | 无 写这篇博客的原因呢是因为自己在编写基于Nginx磁盘缓存管 ...

  5. Android常见的内存泄漏分析

    内存泄漏原因 当应用不需要在使用某个对象时候,忘记释放为其分配的内存,导致该对象仍然保持被引用状态(当对象拥有强引用,GC无法回收),从而导致内存泄漏. 常见的内存泄漏源头 泄漏的源头有很多,有开源的 ...

  6. java程序内存泄漏场景及预防

    为什么80%的码农都做不了架构师?>>>    虽然jvm有垃圾回收机制,如果程序编写不注意某些特定规则,仍然会导致java程序内存泄漏,最终可能出现OutOfMemory异常. 1 ...

  7. android webview关闭后资源不释放,【Android】 WebView内存泄漏优化之路

    这几年H5的快速发展,使得Hybrid混合开发越来越流行,而webview也成为了开发中必备的元素.但是我们知道WebView在加载页面时,会占用非常大的内存,无论是iOS还是Android系统上,加 ...

  8. Android中的内存泄漏和内存溢出

    一.内存泄漏 1.内存泄漏的现象和本质 内存泄漏(Memory Leak)是指某些对象已经不再使用了,但却无法被垃圾回收器回收内存,还一直占用着内存空间的现象,这就导致这一块内存泄露了. 而垃圾回收器 ...

  9. Unix下C程序内存泄漏检测工具Valgrind安装与使用

    Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...

最新文章

  1. Python识别文字,实现看图说话 | CSDN博文精选
  2. 全球Top1000计算机科学家h指数发布,数据院院长Philip S. Yu上榜(附完整名单)...
  3. MySql The service could not be started
  4. mysql 语法积累
  5. 单纯形法只有两个约束条件_10分钟掌握对偶单纯形法
  6. OpenCV学习笔记十一-findcounters函数
  7. 【HDU - 1518】Square (经典的dfs + 剪枝)
  8. LeetCode 1361. 验证二叉树(图的出入度)
  9. socket网络编程python_python之路8:Socket网络编程
  10. relative与absolute相结合
  11. hive开启kerberos-beeline连接
  12. 微信小程序服务器登入dome,小程序登录Demo
  13. python递归算法案例教案_Python电子教案2-1-Python程序实例解析.ppt
  14. 怎么做应力应变曲线_做了这么多年材料,这些力学性能测试你做对了吗?
  15. 安卓系统怎么样不Root激活XPOSED框架的方法
  16. Windows11配置Java开发环境
  17. 监督学习、无监督学习、半监督学习和强化学习
  18. Origin绘图后导出图片的方法
  19. 我的博客园博客开通咯(qyl)
  20. chrome 插件 click 无效

热门文章

  1. Windows下虚拟机的自动化管理
  2. Android LayoutParams源码分析
  3. libflex cydia源
  4. html中加法计算器的代码,JavaScript实现简易加法计算器
  5. 【炼丹炉】ubuntu18.04离线安装gcc
  6. 基于52单片机的超声波测距模块(hc-sr04超声波测距模块+1602液晶显示器)
  7. 安装软件时出现System Pending Reboot
  8. GDOUCTF2023 WriteUP by 肥猪拱菜队
  9. 实习一个月后的第一篇日记(一)
  10. 基于javaweb+jsp的银行信息管理系统(JavaWeb MySQL JSP Bootstrap Servlet SSM SpringBoot)