登录 / 注册
IT168技术开发频道
IT168首页 > 技术开发 > 技术开发评论 > 正文

如何解决Java的头号杀手—内存泄漏!

2017-07-17 10:02    it168网站原创  作者: 邹铮翻译 编辑: 田晓旭

  【IT168 评论】笔者曾经听到同事在一次会议发表如下声明:如果你是Android开发人员,并且你不使用WeakReferences,那你就会有麻烦。

  毫无疑问,有人可能会争论WeakReferences是否真的这么重要,但这其中隐藏着Java领域最大的问题之一:内存泄漏。

  内存泄漏

  内存泄漏是无声的杀手,它们在初始孵化期间慢慢滋生,没有任何人发现。随着时间推移,它们不断成长、堆积和积累。当你意识到它们的存在时已经太迟:你的整个代码库充满内存泄漏,寻找解决方案需要付出巨大努力。因此,我们应该尽可能在早期阶段发现内存泄漏。

  让我们从头开始。

  什么是内存泄漏?

  当不再使用的对象仍被其他对象引用到内存时,便会发生内存泄漏。这在Android世界特别麻烦,因为Android设备内存量非常有限,有时候只有16MB。你可能认为这足以运行应用程序,但请相信我:你很快会超越这个限制。

  吃掉可用内存是最直接的结果,但低内存还有一个有趣的副作用:垃圾收集器(GC)将开始更频繁地触发。当GC触发时,世界停止。应用需要每隔16毫秒渲染一帧,而随着GC开始运行,此帧速率可能会受到影响。

  泄漏如何发生?

  那么,你是如何让内存泄漏发生的呢?让我们看看我们如何可引起内存泄漏:

  没有关闭开放流。我们通常打开数据流连接到数据库池、到开放网络连接或者开始读取文件,没有关闭它们会造成内存泄漏。

  使用静态字段来保存引用。静态对象总是在内存中,如果你声明太多静态字段来保存引用得到对象,这会造成内存泄漏。对象越大,内存泄漏就越大。

  利用不正确使用hashCode() (或根本不用)或equals()的HashSet。这样的话,HashSet会开始变大,对象将被重复插入!实际上,当笔者被问及“HashSet中hasCode()和equals()的目的是什么”时,笔者总是有相同的答案:避免啊内存泄漏!也许这不是最务实的答案,但确实如此。

  如果你是Android开发人员,内存泄漏的可能性会呈指数级增长。Context对象主要用于访问和加载不同的资源,它作为参数被传递给很多类和方法。

  想像一下旋转屏幕的情况。在这种情况中,Android会破坏当前活动,并师徒在旋转发生之前重新创建相同的状态。在很多情况下,如果我们假设你不想重新加载长的Bitmap,你需要保持静态引用,以避免Bitmap被重新加载。这个问题是,Bitmap通常在Drawable实例化,它最终与其他元素链接,并被链接到Context级别,泄漏整个类。这也是为什么应该非常小心静态类的原因之一。

  我们如何避免内存泄漏?

  还记得我们前面讨论过WeakReferences?让我们看看Java中不同类型的引用:

  Normal:这是主要参考类型。它对应于对象的简单创建,当该对象不再使用和引用时将会被收集。这只是典型的对象实例化: SampleObject sampleObject = new SampleObject();

  Soft:这是个不够强大单独引用,当垃圾收集器事件触发时它无法将对象保存在内存中。因此在执行期间的任何时候它可为空。通过使用这个引用,垃圾收集器会根据系统需求决定何时释放对象内存。为了使用该引用,只要创建一个SoftReference对象将实体对象作为参数传递给构造函数,并调用SoftReference.get()来获取对象: SoftReference<SampleObject> sampleObjectSoftRef = new SoftReference<SampleObject>(new SampleObject()); SampleObject sampleObject = sampleObjectSoftRef.get();

  Weak:这就像SoftReference,但更弱;

  Phantom:这是最弱的引用;该对象可用于析构。这种类型的引用很少被使用, PhantomReference.get()方法通常返回null,这个引用目前我们不感兴趣,但知道这种引用也是有用单独。

  如果我们知道哪些对象有较低的优先级以及可被收集而不会在应用程序正常执行中引起问题,这些类可能会有用,让我们来看看如何使用它们:

如何解决Java的头号杀手—内存泄露!

  非静态内部类被广泛用于Android,因为它们允许我们访问外啊不类的ID,而不直接传递它们的引用。然而,Android开发人员通常会添加内部类来节省时间,而没有意识到对内存性能的影响。当Activity启动时,简单单独AsyncTask被创建并执行。但内部类需要可访问外部类,所以每次Activity被破坏时都会发生内存泄漏,但AsyncTask仍然工作。这不仅在调用Activity.finish()时发生,当因为配置变更或内存需求Activity被系统强制破坏时也会发生,并且,它会被再次创建。AsyncTask保存对每个Activity的引用,使其在销毁时无法被垃圾收集。

  考虑一下,当用户在任务运行时旋转设备会发生什么情况:整个Activity实例需要始终可用知道AsyncTask完成。此外,大部分时候我们想要AsyncTask使用AsyncTask.onPostExecute()方法将结果显示在屏幕。这可能导致崩溃,因为当任务仍然在运行时Activity被破坏,查看引用可能为空。

  那么,解决方案是什么呢?如果我们将内部类设置为静态,我们无法访问外部类,所以我们需要提供引用。。为了增加两个实例之间的距离,以及让垃圾收集器正常配合Activity工作,让我们使用更弱的引用来实现更清晰的内存管理,前面的代码更改为:

如何解决Java的头号杀手—内存泄露!

  这样的话,这些类被分离,Activity不再使用时将被收集,AsyncTask对象不会在WeakReferences对象内找到Activity实例,也不会执行AsyncTask.onPostExecute()方法代码。

  通过正确使用引用,我们可使用这些方法来避免在代码中引发内存泄漏:

  避免在Activity中使用非静态内部类,使用静态内部类并进行WeakReferences

  当你可选择使用Context时,尝试使用Activity Context而不是Application Context

  一般来说,永远不要引用到任何类型的Context

标签: Java
相关文章
  • IT168企业级IT168企业级
  • IT168文库IT168文库

扫码送文库金币

编辑推荐
系统架构师大会
系统架构师大会
点击或扫描关注
IT168企业级微信关注送礼
IT168企业级微信关注送礼
扫描关注
首页 评论 返回顶部