如何在您的Java应用中查找并修复内存泄漏

【】您是否碰到过某个Java应用程序起初运行良好,经过一段时间后却缓慢下来了?或者它在处理少量文件时性能不错,文件量一旦增加就性能下降的情况呢?如出现这样的情况,很可能您遇到了内存泄漏的问题。

在应对内存泄漏时,如果有人问我:“你是否知道此事的前因后果和应对方法?”那么,我就会做出如下回答:

一、目标受众

尽管在一般情况下,本文中所介绍的方法是独立于IDE和操作系统的,但是我在此所用到的截图和说明仍然来自于Fedora Linux和带插件开发的Eclipse。

二、内存泄漏的症状

起初运行速度快,但随着时间的推移速度就慢下来了。比如说:

  • 能够正常处理少量数据集,但应对大量数据集时出现严重的性能问题。
  • 在您的JVM中,旧版本(Old-Generation)内存的使用率持续增加。
  • 在您的JVM中,出现内存耗尽的跳转错误。
  • 无故自我崩溃。

三、常见的内存泄漏

Java中的内存泄漏通常发生在您忘记关闭某个资源,或是某个对象的引用没能释放的时候。例如:

  • 文件/文本缓冲区没被关闭。(请参见:https://git.eclipse.org/r/#/c/31313/中的案例)
  • 在equals()和hashcode()不被使用时,各种哈希映射的引用仍然保持激活的状态,例如:
    1. import java.util.Map; 
    2. public class MemLeak { 
    3. public final String key; 
    4. public MemLeak(String key) { 
    5.     this.key = key; 
    6. public static void main(String args[]) { 
    7.     try { 
    8.       Map map = System.getProperties(); 
    9.       for(;;) { 
    10.          map.put(new MemLeak("key"), "value"); 
    11.       } 
    12.     } catch(Exception e) { 
    13.         e.printStackTrace(); 
    14.     } 
    15.   } 
  • 其他各种细节(请参见:https://www.toptal.com/java/hunting-memory-leaks-in-java#memleak)
  • 那些引用了各种外部类的内部类所导致的泄漏。(可以将它们变成静态来避免,请参见:https://blogs.oracle.com/olaf/entry/memory_leaks_made_easy)。

四、如何一次性修复它们?

这里提供两种方法。第一种是尝试“快速修复”。如果此法失败,那么您就必须往下尝试一条漫长的解决之路了。

  1. 快速修复:使用Eclipse内存泄漏的警告(去捕捉一些泄漏)。
  2. 手动禁用和启用您代码的各个部分,并使用VisualVM(Jconsole或Thermostat)之类的工具观察JVM的内存使用情况。

1. 快速修复:Eclipse内存泄漏的警告/错误。

为了遵从JDK 1.5+的代码规范,Eclipse会向您“抛出”一些明显泄漏用例的警告和错误。更精确地说,任何使用了closable(如1.5后出现的outputStream)的对象,如果它的引用是被销毁而不是封闭的话,就会抛出一个警告。然而在Eclipse的各个项目中,其检漏功能并非总是被启用的。因此,为了事先打开它们,您可以到项目的设置里,按照下图所示进行开启:

此处Eclipse罗列出了各种内存泄漏:

然而,就算使用了Eclipse的此项功能,系统仍无法探测到所有的文件关闭与泄漏。尤其是在使用旧式(1.5之前)代码时,您很可能会因为它们在使用过程中仅仅只是“关闭”(closable)了,而遇到泄漏问题了。也有时候,文件在深度嵌套中被打开/关闭,也会导致Eclipse无法检测到。因此如果您碰到这种情况,就可能需要去尝试第2种方法了。

2. 手动禁用和启用您代码的各个部分,并使用VisualVM之类的工具观察JVM的内存使用情况。

如果您步入了这一步,那就不得不卷起袖子,做一些体力劳动了。您需要通读您的所有代码,以试图找出发生泄漏的地方。作为帮助,我建议您使用VisualVM之类的工具(当然,Thermostat和MAT也是可行的)。

a. 配置的VisualVM

(1) 下载该工具。

(2) 打开终端,到达目录…/visualvm_xyz/bin下,运行shell脚本’./visualvm’ (或在Windows上运行visualvm.exe)。

(3) 您会看到弹出的主窗口。如果展开“本地”并双击您正在运行的应用(如下图,我的应用是一个子Eclipse),您就可以看到它的各种属性。

(4) 在Fedora上用VisualVM进行故障诊断:对我来说,最初我无法连接到自己的JVM,也不能够使堆转储(heap-dumps)和分析(profiling)运行起来。于是我探索出了如下步骤:

  • 确保用自己的登录用户身份运行它,而不是使用sudo。
  • 对系统进行全面更新(sudo yum update)。
  • 考虑重新启动是否有所帮助。
  • 尝试在关闭所有正在运行的Java应用程序之后,再启动VisualVM。

(5) 添加一些插件。在使用VisualVM之前,我事先添加了一些插件。请点击进入工具->插件->“可用插件”。请选择如下的插件(如果您喜欢,则可以随意浏览并添加更多的插件):

  • 内存池
  • 可视的GC
  • 终止应用程序

b. 用VisualVM分析运行的代码

(1) 现在运行您的Java应用程序。

(2) 将VisualVM连接到您的应用程序。

(3) 执行那些容易导致性能变缓的操作。

(4) 检查“监控”和“内存池”选项卡。如果您看到在“监视器”选项卡中内存显示增加的话,那就按下“执行GC”(垃圾收集),并监视内存的使用情况是否有所减少。

(5) 如果并不减少的话,那么就切换到“内存池”选项卡,并检查“Old Gen”(最开始的对象会停留在“Eden”中,然后通过Survivor空间进行过渡,比较旧的对象会被移到“Old Gen”池中。如果出现泄漏,则会出现在Old-Gen池里。)。

(6) 现在返回去,并注释掉程序代码的大部分,从而定位到应用程序开始变慢的位置。

(7) 重复上述过程,直到应用程序完全不再有泄漏的发生。

(8) 然后,经过反复迭代来重新启用代码的各个部分,并检查VisualVM的内存使用情况。一旦您的应用程序再次开始泄漏,则马上进入导致内存泄漏的该函数方法,从而进一步缩小代码的考察范围。

(9) 最终,您将能够把问题缩小到具体某一个类,甚至某一个单一的方法上。请仔细验证所有文件的缓冲区是否已被关闭,而HashMap是否被正确的使用了。

五、标准化您的代码

有时候会很难确定您那“金光闪闪”的新代码是否真的会比旧代码更好。面对这种情况下,您需要去标准化应用程序的性能。您可以将下面的这段代码插入到任何您认为适当的位置,以获取有关运行时间和垃圾收集次数的相关信息:

  1. long start = System.currentTimeMillis(); 
  2. .. 
  3.  //your code 
  4. .. 
  5. long end = System.currentTimeMillis(); 
  6. System.out.println("Run time: " + Long.toString(end - start)); 
  7. System.out.println(printGCStats()); 
  8. public static String printGCStats() { 
  9.  long totalGarbageCollections = 0
  10.  long garbageCollectionTime = 0
  11.  for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) { 
  12.  long count = gc.getCollectionCount(); 
  13.  if (count >= 0) { 
  14.  totalGarbageCollections += count; 
  15.  } 
  16.  long time = gc.getCollectionTime(); 
  17.  if (time >= 0) { 
  18.  garbageCollectionTime += time; 
  19.  } 
  20.  } 
  21.  return "Garbage Collections: " + totalGarbageCollections + "n" + 
  22.  "Garbage Collection Time (ms): " + garbageCollectionTime; 

特别提醒一下:如果您是在主Eclipse中进行测试的话,我建议去测试一个“干净”的子Eclipse;或者是在您的Eclipse的一些“干净”实例中进行。因为这样的话,其他各种插件是不会对标准的耗时产生影响的。

六、附加说明:堆转储

我个人使用的并不多,但有些人比较热衷于“堆转储”。您可以在任何时候采取堆转储,然后查看有多少类的实例被打开,以及它们使用了多大的空间。您可以通过双击它们来查看具体的内容。如果您想获悉自己的应用程序产生了多少个对象的话,这种方法会非常有用。

七、我的应用并没有泄漏,可为何还是很慢?

当然也存在着一种可能性:就算您的代码中并没有任何的泄漏,它仍然运行缓慢。如果出现这种情况的话,您就必须进行代码分析了。不过,代码分析已经超出了本文所涉及的范围。这里推荐一个很好的YouTube视频,它讲解了如何去使用免费和付费的分析器来对Eclipse进行分析,请参见:https://www.youtube.com/watch?v=YCC-CpTE2LU。

八、还能看哪些?

至此您可以潜下心来,花上一到两天的时间去修复您的内存泄漏问题了。在此过程中,如果您仍碰到麻烦的话,请参考如下的链接:

  • 捕捉内存泄漏:https://www.toptal.com/java/hunting-memory-leaks-in-java
  • 内部类的内存泄漏问题:https://blogs.oracle.com/olaf/entry/memory_leaks_made_easy
  • 浏览Oracle的JVM GC指南:www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html

原标题:How to Find and Fix Memory Leaks in Your Java Application,作者: Leo Ufimtsev

【译稿,合作站点转载请注明原文译者和出处为.com】

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/130283.html<

(0)
运维的头像运维
上一篇2025-02-26 00:28
下一篇 2025-02-26 00:29

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注