如何应对Java内存泄漏。工具、修复及更多

314 阅读15分钟

内存管理是Java的强项,也是开发者选择Java而不是其他平台和编程语言的众多原因之一。在纸面上,你创建对象,而Java部署其 垃圾收集器 来分配和释放内存。但这并不是说Java是无懈可击的。事实上,内存泄漏会发生,而且在Java应用程序中经常发生。

我们把这个指南放在一起,让你掌握检测、避免和修复内存泄漏的技术。 Java中的内存泄漏.

你应该担心内存泄漏吗?

内存泄漏通常涉及少量的内存资源,你可能不会想到会有问题。但是,当你的应用程序返回 _java.lang.OutOfMemoryError_时,那么你的第一个也是最有可能的嫌疑就是内存泄露。

内存泄漏通常是程序写得不好的一个指标。如果你是那种希望一切都完美的程序员,你应该调查你遇到的每一个内存泄漏。作为一个Java程序员,没有办法知道Java虚拟机何时会运行垃圾收集器。这是真的,即使你指定 System.gc()。当内存不足或可用内存少于你的程序所需时,垃圾收集器可能会运行。如果垃圾收集器没有释放出足够的内存资源,你的程序将从操作系统中获取内存。

与发生在C++和其他编程语言中的内存泄漏相比,Java的内存泄漏并不总是很严重。 根据IBM developerWorks的Jim Patrick的说法,考虑到内存泄漏,有两个因素你应该关注。

  1. 泄漏的大小
  2. 程序的寿命。

一个小的Java程序可能会有内存泄漏,但如果JVM有足够的内存来运行你的程序,这并不重要。然而,如果你的Java程序不断运行,那么内存泄漏将是一个问题。这是因为一个持续运行的程序最终会耗尽内存资源。

另一个可能出现内存泄漏问题的领域是,当程序调用大量临时对象,占用大量内存的时候。当这些占用内存的对象没有被取消引用时,程序的可用内存很快就会比需要的少。


New call-to-action


如何避免Java内存泄漏

为了避免内存泄漏,你需要注意你的代码编写方式。以下是帮助你杜绝内存泄漏的具体方法。

1.使用引用对象来避免内存泄漏

Raimond Reichert在JavaWorld上 写道,你可以使用引用对象来摆脱内存泄漏。

使用 java.lang.ref 包,你可以在你的程序中与垃圾收集器合作。这可以让你避免直接引用对象,而使用特殊的引用对象,让垃圾收集器轻松清除。特殊的子类允许你间接地引用对象。例如,Reference有三个子类。PhantomReference、SoftReference和WeakReference。

一个参照物,或者被这些子类引用的对象,可以使用该参照物的get方法进行访问。使用这种方法的好处是,你可以通过将引用设置为null来轻松地清除引用,而且引用几乎是不可改变的。垃圾收集器是如何处理每种类型的引用的?

  • SoftReference对象:当内存不足时,垃圾收集器需要清除所有SoftReference对象。
  • WeakReference对象:当垃圾收集器感觉到一个弱引用的对象时,所有对它的引用都会被清除,并最终从内存中取出。
  • PhantomReference对象:垃圾收集器无法自动清理PhantomReference对象,让你手动清理所有PhantomReference对象和引用。

使用引用对象,你可以与垃圾收集器合作,自动完成删除弱联系的监听器的任务。WeakReference对象,特别是与清理线程一起,可以帮助你避免内存错误。

2.避免与WebApp类加载器有关的内存泄漏

使用Jetty 7.6.6.或更高版本,你可以防止WebApp类加载器钉住。当你的代码不断引用WebApp类加载器时,内存泄漏很容易发生。在这种情况下,有两种类型的泄漏: 守护线程静态字段

  • 静态字段 是以classloader的值开始的。即使Jetty停止部署,然后重新部署你的Web应用,静态引用也会持续存在,所以对象不能从内存中被清除。
  • 在Web应用的生命周期之外启动的Daemon线程,很容易 发生内存泄漏,因为这些线程有对启动线程的classloader的引用。

使用Jetty,你可以使用防止器来帮助你解决与WebApp类加载器相关的问题。例如,应用程序上下文泄漏预防器,如 appcontext.getappcontext(), 帮助你将静态引用保持在上下文类加载器内。你可以使用的其他防止器包括以下几种。

  • AWT泄漏预防器
  • DOM 泄漏预防器
  • 驱动程序管理器泄漏预防器
  • GC线程泄漏预防器
  • Java2D泄漏预防器
  • 防范LDAP泄露
  • 登录配置泄漏预防器
  • 安全供应商泄漏预防器

3.其他具体步骤

BurnIgnorance.com 还列出了几种防止Java中内存泄漏的方法,包括。

  • 在不再需要会话时释放它。使用HttpSession.invalidate()来做到这一点。
  • 保持每个会话的超时时间较短。
  • 在你的HttpSession中只存储必要的数据。
  • 避免使用字符串连接法。使用StringBuffer的append()方法,因为字符串是一个不可改变的对象,而字符串连接法会产生许多不必要的对象。大量的临时对象会减慢性能。
  • 尽可能地,你 不应该 在你的jsp页面上创建HttpSession。你可以通过使用页面指令<%@page session="false"%>来做到这一点。
  • 如果你正在编写一个 经常执行的查询, 使用PreparedStatement对象而不是使用Statement对象。为什么?PreparedStatement是预编译的,而Statement是在你的SQL语句每次传送到数据库时才编译的。
  • 当使用JDBC代码时,当你写查询时避免使用 "*"。尽量使用相应的列名来代替。
  • 如果你要在一个循环中使用stmt = con.prepareStatement(sql query),那么一定要在这个特定的循环中关闭它。
  • 当你需要重新使用Statement和ResultSet时,一定要关闭这些东西。
  • 在最后一个块中关闭ResultSet、Connection、PreparedStatement和Statement。

当你怀疑有内存泄漏时该怎么做

如果你发现执行你的应用程序需要更长的时间,或者注意到相当大的速度减慢,那就是检查内存泄漏的时候了。

你怎么知道你的程序有内存泄漏? 一个普遍的迹象是java.lang.OutOfMemoryError错误。这个错误有几个详细的信息,可以让你确定是否有内存泄漏。

  • Java堆空间:无法为Java堆中的特定对象分配内存资源。这可能意味着几种情况, 包括内存泄漏或指定的堆大小比应用程序需要的要低 ,或者你的程序使用了大量的终结者。
  • PermGen空间:永久生成区已经满了。这个区域是存储方法和类对象的地方。你可以通过-_XX:MaxPermSize_增加空间来轻松纠正。
  • 请求的数组大小超过了虚拟机的限制:程序正试图分配一个大于堆大小的数组。
  • 请求字节的<原因>。交换空间用完了?:使用本地堆的分配没有成功,或者本地堆接近被用完。
  • <原因><栈跟踪>(本地方法):一个本地方法没有分配到所需的内存。

不太常见的内存泄漏

有些时候,你的应用程序崩溃时并没有返回OutOfMemoryError消息,这使得诊断内存泄漏的问题并进行纠正变得更加困难。好消息是,你可以检查致命的日志错误或崩溃转储来查看出了什么问题。

此外,有许多监控和诊断工具,你可以用来帮助识别和纠正内存泄漏。 Stackify的Darin Howard 指出, Java剖析 器是追踪内存泄漏和手动运行垃圾收集器的绝佳方法。你可以使用Java剖析器来审查内存的使用情况,这将很容易让你看到使用过多内存的进程和类。你还可以使用JVM性能指标,它可以给你提供大量关于垃圾收集、线程数和内存使用的数据。

关于Java剖析器的简短说明

Java剖析帮助你监控不同的JVM参数,包括对象创建、线程执行、方法执行以及垃圾收集。

当你已经排除了内存泄漏作为你的应用程序速度减慢的原因时,使用Java剖析工具来更近距离地了解你的应用程序是如何利用内存和其他资源的。与其翻阅你的代码来发现问题,不如简单地使用这些工具,这将为你节省所需的时间和精力,以确保你的代码是合格的。

Java剖析器给你一套全面的统计数据和其他信息,你可以用来追踪你的编码错误。剖析器还可以帮助你找到导致性能减慢、多线程问题和内存泄漏的原因。简而言之,剖析器给你一个更稳定和可扩展的应用程序。最重要的是,这些Java剖析工具会给你一个关于每个问题的精细分析以及如何解决它们。

Java 剖析度量

如果你在项目早期就使用这些工具,并定期使用--特别是与其他 Java性能工具一起使用 --你可以创建高效、高性能、快速和稳定的应用程序。剖析工具还可以帮助你在部署应用之前了解关键问题。

使用Java剖析工具可以发现的一些指标包括。

  • 一个方法的CPU时间
  • 内存利用率
  • 关于方法调用的信息
  • 创建了哪些对象
  • 哪些对象被从内存中删除或垃圾回收

Java剖析器 内存分析器(MAT 允许你分析Java堆,以搜索内存外观和降低内存使用。你可以很容易地分析堆转储,即使有数以百万计的对象生活在其中,看到每个对象的大小以及为什么垃圾收集器没有从内存中删除特定的对象。MAT会给你一份关于这些对象的灵巧报告,帮助你缩小可疑的内存泄漏。

Java飞行记录器 是一个诊断和剖析工具,为你提供关于正在运行的应用程序的更多信息,而且通常比其他工具提供的数据更好。Java飞行记录器允许第三方服务创建的API,并降低了你的总成本。作为Oracle Java SE的一项商业功能,Java Flight Recorder还为你提供了一种简单的方法来检测内存泄漏,找到对这些泄漏负责的类,并定位泄漏点以纠正它。

你应该知道的其他工具

  • NetBeans Profiler - 支持Java SE、Java FX、EJB、移动应用和Web应用,可用于监控内存、线程和CPU资源。
  • JProfiler - 一个线程、内存和CPU剖析工具,也可用于分析内存泄漏和其他性能瓶颈。
  • GC Viewer - 一个开源的工具,可以让你轻松地将JVM产生的信息可视化。你可以使用GC浏览器查看与垃圾收集有关的性能指标,包括累计暂停、最长暂停和吞吐量。除了使你能够运行垃圾收集之外,你还可以使用这个工具来设置初步的堆大小。
  • VisualVM - 基于NetBeans平台,VisualVM是一个易于扩展的工具,使用各种插件为你提供关于你的应用程序的详细数据,用于监控远程和本地应用程序。你可以使用这个工具获得内存剖析和手动运行垃圾收集器。
  • Patty in action --另一个开源工具,你可以用它作为剖析工具,给你提供目标和深入的剖析。你可以用这个工具来分析堆。
  • JRockit - 来自Oracle的专有解决方案,JRockit是针对Java SE应用程序的,可以用来预测延迟,可视化垃圾收集和整理与内存有关的问题。
  • GCeasy - GCeasy是一个分析与垃圾收集有关的日志的工具,在分析垃圾收集日志时,它是检测内存泄漏问题的一个简单方法。使用GCeasy的另一个原因是,它可以在线使用;不需要在你的机器上安装就可以使用它。

Java内存泄漏。解决方案

现在你知道你的程序有内存泄漏,你可以使用这些工具来帮助修复泄漏,当它们成为一个问题时--最好是在泄漏成为问题之前。

使用可以检测内存泄漏的工具

对于我们的下一个例子,我们将使用VisualVM。

一旦你下载并配置了VisualVM,通过运行连接了VisualVM的应用程序来分析你的代码。当执行使你的应用程序变慢的任务时,VisualVM会查看 "监视器 "和 "内存池 "标签。你需要注意什么?当你在 "监控 "选项卡中看到内存使用的峰值时,按 "执行GC "按钮,这将激活垃圾收集。这应该有助于减少内存的使用量。

如果这不起作用,切换到 "内存池 "并查看 "旧基因 "部分。如果对象正在泄漏,你会在这里看到它。请记住,活跃的对象被放置在 "伊甸园",然后会被移到 "幸存者"。同时,较老的物体会在 "旧世代 "池中找到。

在这一点上,你可以回到你的代码中,注释掉不相关的部分,直到你注意到有性能减慢或它刚刚停止的地方。重复所有这些步骤,直到你消除了所有的泄漏。

启用你的代码的某些部分来检查内存的使用情况,如果你发现另一个泄漏,就进入导致这些泄漏的方法来帮助堵塞它。继续缩小范围,直到你只剩下一个类或方法。验证所有的文件缓冲区,看看这些缓冲区是否被关闭。另外,检查所有的哈希图,看看你是否正确地使用了这些哈希图。

使用堆转储

如果你觉得上述方法太过繁琐,你也许可以通过使用堆转储来减少你在修复内存泄漏上花费的时间。堆转储允许你看到打开的实例数量以及这些实例占用的空间。如果有一个特定的实例你想进一步调查,你只需双击那个特定的实例就可以看到更多信息。堆转储可以帮助你了解你的应用程序到底产生了多少个对象。

使用Eclipse内存泄漏警告

另一个节省时间的方法是依靠Eclipse的内存泄漏警告。如果你的代码符合 JDK 1.5 或更高版本,你可以使用 Eclipse 在引用结束但对象持续存在且未关闭时向你发出警告。只要确保在你的项目设置中启用泄漏检测。请注意,使用Eclipse可能不是一个全面的解决方案。Eclipse不能检测到所有的泄漏,可能会错过一些文件的关闭,特别是当你的代码不符合JDK 1.5(或更高)的时候。另一个原因是,Eclipse并不总是有效,因为这些文件关闭和打开的嵌套非常深。

其他资源和教程

从以下资源和教程中获得更多关于如何避免、检测和纠正内存泄漏的见解和信息。

摘要

内存泄露当然是Java开发者关心的问题,但它们并不总是世界的末日。用知识来武装自己,在它们发生之前预防它们,并在它们出现时解决它们。

Stackify by Netreo 正在快速增长,我们的服务的使用也在快速增长。如果您的应用程序不断变化或使用量不断增加,那么您必须拥有良好的工具来监测和寻找性能问题的根本原因。 在Java中构建更好的代码是很容易的-- Prefix 提供了一个即时反馈回路,让你在编写代码时就能看到你的应用程序的性能,而 Retrace 为你的所有Java应用程序提供强大的应用程序性能管理(APM)。

相关的。 11个简单的Java性能调优技巧

关于Alexandra Altvater