如何处理Java内存泄漏:检测工具、修复程序插图

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

本文是为了让您掌握如何检测、避免和修复Java中的内存泄漏。

你应该担心内存泄漏吗?

内存泄漏通常涉及少量内存资源,您可能不希望这些资源出现问题。但当您的应用程序返回java.lang.OutOfMemoryError时,您的第一个也是最有可能的怀疑就是内存泄漏。

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

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

  • 泄漏的大小
  • 程序的生命周期。

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

内存泄漏可能是一个问题的另一个方面是当程序调用大量占用大量内存的临时对象时。当这些占用内存的对象没有被反引用时,程序的可用内存很快就会少于需要的内存。

如何避免Java内存泄漏

为了避免内存泄漏,您需要注意如何编写代码。以下是帮助您消除内存泄漏的具体方法。

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

JavaWorld的Raimond Reichert写道,可以使用引用对象来消除内存泄漏。

使用java.lang.ref包,您可以在程序中使用垃圾收集器。这允许您避免直接引用对象,并使用垃圾收集器轻松清除的特殊引用对象。特殊的子类允许您间接引用对象。例如,Reference有三个子类:PhantomReferenceSoftReferenceWeakReference

可以使用引用对象的get方法访问引用对象或这些子类引用的对象。使用此方法的优点是,通过将引用设置为null,可以轻松清除引用,并且该引用几乎是不可变的。垃圾收集器如何处理每种类型的引用对象?

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

使用引用对象,您可以使用垃圾收集器来自动执行删除弱可访问侦听器的任务。WeakReference对象,特别是带有清理线程的对象,可以帮助您避免内存错误。

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

使用Jetty7.6.6。或者更高,您可以防止WebApp类加载器固定。当您的代码不断引用WebApp类加载器时,很容易发生内存泄漏。在这种情况下有两种类型的泄漏:守护进程线程静态字段

  • 静态字段以类加载器的值开始。即使Jetty停止部署然后重新部署web应用程序,静态引用仍然存在,因此无法从内存中清除对象。
  • 在web应用程序生命周期之外启动的守护进程线程,由于这些线程引用了启动线程的类加载器,因此容易发生内存泄漏。

使用Jetty,您可以使用防喷器来帮助解决与WebApp类加载器相关的问题。例如,应用程序上下文防泄漏程序,如appcontext.getappcontext(),可以帮助您将静态引用保留在上下文类加载器中。您可以使用的其他preventers包括:

  • AWT leak preventer
  • DOM leak preventer
  • Driver manager leak preventer
  • GC thread leak preventer
  • Java2D leak preventer
  • LDAP leak preventer
  • Login configuration leak preventer
  • Security provider leak preventer

3.其他具体步骤

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

  • 不再需要会话时释放会话。使用HttpSession.invalidate()来执行此操作。
  • 为每个会话保持较低的超时时间。
  • 仅在HttpSession中存储必要的数据。
  • 避免使用字符串连接。使用StringBufferappend()方法,因为字符串是不可更改的对象,而字符串串联会创建许多不必要的对象。大量临时对象将降低性能。
  • 尽可能不要在jsp页面上创建HttpSession。您可以使用页面指令<%@page session=”false”%>
  • 如果您正在编写一个频繁执行的查询,请使用PreparedStatement对象而不是Statement对象。为什么?PreparedStatement是预编译的,而每次SQL语句传输到数据库时都会编译语句。
  • 使用JDBC代码时,请避免在编写查询时使用“*”。请尝试改用相应的列名。
  • 如果要在循环中使用stmt=con.prepareStatement(sql查询),请确保在该特定循环中关闭它。
  • 当需要重用语句和结果集时,请确保关闭它们。
  • 关闭最后一个块中的ResultSetConnectionPreparedStatementStatement

如果怀疑内存泄漏,该怎么办?

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

如何知道程序存在内存泄漏?一个常见的符号是java.lang.OutOfMemoryError。此错误包含多条详细消息,允许您确定是否存在内存泄漏:

  • Java堆空间:无法为Java堆中的特定对象分配内存资源。这可能意味着几件事,包括内存泄漏或指定的堆大小低于应用程序需要的大小,或者您的程序正在使用大量终结器。
  • 永久代空间:永久代区域已满。此区域是存储方法和类对象的位置。您可以通过–XX:MaxPermSize增加空间来轻松更正此问题。
  • 请求的数组大小超过VM限制:程序正在尝试分配大于堆大小的数组。
  • 为<reason>请求<size>字节。交换空间不足?:使用本地堆的分配未成功,或者本机堆即将用尽。
  • <Reason><stack trace>(本机方法):未为本机方法分配所需内存。

较少常见的内存泄漏

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

此外,您可以使用许多监视和诊断工具来帮助识别和纠正内存泄漏。Stackify的Darin Howard认为Java profilers是追踪内存泄漏和手动运行垃圾收集器的一种极好的方法。您可以使用Java profilers来查看内存的使用情况,这将很容易向您显示使用了过多内存的进程和类。您还可以使用JVM性能指标,它为您提供大量关于垃圾收集、线程计数和内存使用的数据。

关于Java profilers的简短介绍

Java profiling可以帮助您监视不同的JVM参数,包括对象创建、线程执行、方法执行和垃圾收集。

当您排除了内存泄漏是应用程序运行缓慢的原因时,请使用Java评测工具更仔细地了解应用程序是如何利用内存和其他资源的。不要使用代码来查找问题,只需使用这些工具,这将节省您所需的时间和精力,以确保代码达到标准。

Java profilers为您提供了一组全面的统计信息和其他信息,您可以使用这些信息来跟踪编码错误。profilers还可以帮助您找到导致性能下降、多线程问题和内存泄漏的原因。简而言之,探查器为您提供了一个更稳定、更可扩展的应用程序。最好的部分是,这些Java profilers工具将为您提供对每个问题以及如何解决这些问题的细粒度分析。

Java Profiling指标

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

使用Java评测工具可以找到的一些指标包括:

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

Java profiler内存分析器(MAT)允许您分析Java堆,以搜索内存外观和更低的内存使用率。即使存在数百万个对象,您也可以轻松地分析堆转储,查看每个对象的大小以及垃圾收集器为什么不从内存中删除特定对象。MAT为您提供有关这些对象的漂亮报告,帮助您缩小可疑内存泄漏的范围。

Java Flight Recorder(JFR)是一种诊断和分析工具,它为您提供了有关正在运行的应用程序的更多信息,并且通常比其他工具提供的数据更好。Java Flight Recorder允许第三方服务创建API,并降低您的总体拥有成本。作为Oracle Java SE的一项商业功能,Java Flight Recorder还为您提供了一种简单的方法来检测内存泄漏,找到导致这些泄漏的类,并找到泄漏来纠正它。

其他你应该知道的工具

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

Java内存泄漏:解决方案

现在您知道您的程序存在内存泄漏,您可以使用这些工具在泄漏成为问题时帮助修复泄漏,最好是在泄漏成为问题之前。

使用能够检测内存泄漏的工具

在下一个示例中,我们将使用VisualVM。

下载并配置VisualVM后,通过运行附加VisualVM的应用程序来分析代码。当执行减慢应用程序速度的任务时,VisualVM会查看“监视器”和“内存池”选项卡。你需要注意什么?当您在Monitor选项卡中看到内存使用的峰值时,按下“Perform GC”按钮,这将激活垃圾收集。这将有助于减少使用的内存量。

如果这不起作用,请切换到“内存池”并查看旧的Gen部分。如果物体泄漏,你会在这里看到。请记住,活动对象被放置在Eden“伊甸园”中,然后将被移动到Survivor“幸存者”中。同时,较旧的对象会在Old Gen“旧世代”池中找到。

此时,您可以返回代码并注释掉不相关的部分,直到您注意到性能降低或停止。重复所有这些步骤,直到消除所有泄漏。

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

使用dump堆转储

如果您觉得上面提到的方法太单调乏味,那么可以通过使用堆转储来减少修复内存泄漏所花费的时间。堆转储允许您查看打开的实例数量以及这些实例占用的空间。如果您想进一步调查某个特定实例,只需双击该特定实例即可查看更多信息。堆转储可以帮助您知道应用程序生成了多少对象。

使用Eclipse内存泄漏警告

另一种节省时间的方法是依靠Eclipse内存泄漏警告。如果您的代码符合JDK1.5或更高版本,那么可以使用Eclipse在引用结束但对象仍然存在且未关闭时发出警告。只需确保在项目设置中启用泄漏检测。请注意,使用Eclipse可能不是一个全面的解决方案。Eclipse不会检测到所有泄漏,并且可能会丢失一些文件闭包,特别是当您的代码不符合JDK1.5(或更高版本)时。Eclipse不总是工作的另一个原因是,这些文件闭包和开口嵌套得非常深。

总结

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

原文地址:https://stackify.com/java-memory-leaks-solutions/