Java代码中的内存泄漏,Java程序员面试必备的知识点

现在,我们已经确定我的代码包含内存泄漏。我们如何从那里继续?一种方法是遍历我们的代码并随机删除引用,直到您不再看到错误的行为,但这有点特别,而且很难知道何时完成。相反,我们将使用内存分析器。幸运的是,JDK内置了所有需要的工具。您所要做的就是创建一个堆映像,并对孤立对象进行分析。

基本上可以用两种方法生成堆,每种方法各有优缺点:使用hprof或使用jmap。使用hprof要慢得多,但可以提供更多信息,例如类名和分配堆栈,而jmap全速运行程序,但只提供某些类的名称。这一部分是基于这篇博客文章,但我的文章使选项和阶段之间的区别更清楚,并提供了图片。

使用Hprof

=======

只需使用hprof启动您的程序:

java -Dcom.sun.management.jmxremote \

-Dcom.sun.management.jmxremote.port=9000 \

-Dcom.sun.management.jmxremote.authenticate=false \

-Dcom.sun.management.jmxremote.ssl=false \

-agentlib:hprof=heap=dump,file=hprof.bin,format=b,depth=10 \

-jar java_app.jar

这基本上启动探查器,禁用所有安全性并告诉它转储到一个名为hprof.bin文件. 如果您的程序不在jar文件中,您可以简单地用应用程序的主类替换最后一行。现在练习您的程序,完成后,按Ctrl-C转储堆并退出应用程序。

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

使用Jmap

======

正常启动程序,可以从命令行、文件管理器(Finder或Windows资源管理器或现在Lunix用户使用的任何讨厌的工具)或IDE启动程序。然后找到程序的进程id。您可以用几种不同的方法来实现这一点,但更简单的方法是查看JConsole,在JConsole中可以使用它。在上面的示例中,进程id是59735(请参阅进程选择屏幕上的PID列或监视进程时的标题栏)。现在练习你的程序。

然后使用jmap转储图像:

jmap -dump:format=b,file=hprof.bin 59735

记住用JConsole找到的进程id替换59735。

训练你的程序

======

我们现在从两个不同的分支合并。你的读者没有比你更愉快的阅读体验,但是你呢?嗯,我们需要锻炼我们的计划。也就是说,执行我们认为会导致错误的操作。在我的例子中,我应该启动程序并让它运行,但是你可能需要加载一个文件,打印它或者你的程序做什么。重复几次,这样堆中的错误就更明显了。

分析堆映像

=====

当我们使用任何一种方法生成了一个堆映像时,就应该分析它了。幸运的是,JDK有这样一个工具,jhat(我不知道是谁想出了这些名字,但他们应该被枪杀)。以以下方式开始jhat:

jhat -J-Xmx2G hprof.bin

注意-J和-Xmx2G之间没有空格,我们设置这个选项是为了允许jhat使用2GB的RAM,这可能需要它来分析我们相当大的堆映像。

jhat将处理(这可能需要很长时间),并在本地计算机上的端口7000上启动一个web服务器,因为为什么不呢。要查看生成的数据,请将浏览器指向localhost:7000。你会得到这样的结果:

立即滚动到底部:

选择Show heap histogram链接(注意:你的浏览器可能没有一个红色的矩形指出正确的链接,正如我在后处理中添加的那样)。对于我的应用程序,如果使用hprof,柱状图如下所示:

或者像这样如果我用jhat的话:

数字不同,因为hprof的时间取决于我愿意等待多长时间,而jmap的时间取决于我转储图像的速度。注意,jmap输出并不总是显示好的类名。在下面,我将只显示hprof输出,因为它更好,但是使用jmap输出也可以完成任何事情。

这个输出应该像一个普通的profiler输出一样阅读:关注那些你拥有很多的输出。在我的例子中,我有一大堆公式和HashMap E n t r y 类 。 事 实 上 , 我 比 其 他 任 何 东 西 都 多 了 几 百 个 。 这 是 有 道 理 的 , 我 有 一 堆 公 式 类 , 因 为 我 处 理 公 式 , 但 不 超 过 一 百 万 。 虽 然 数 据 结 构 是 递 归 的 , 但 对 于 一 个 复 杂 的 公 式 , 我 可 能 会 用 几 千 , 而 不 是 一 百 万 。 另 外 , H a s h M a p Entry类。事实上,我比其他任何东西都多了几百个。这是有道理的,我有一堆公式类,因为我处理公式,但不超过一百万。虽然数据结构是递归的,但对于一个复杂的公式,我可能会用几千,而不是一百万。另外,HashMap Entry西HashMap条目也吸引了我。这表示某个东西存储在HashMap中,并且比较这些数字,表明某些东西可能只是公式。查看Formula类没有显示HashMap,所以我有点不知所措:什么包含所有这些公式?

为了调查,让我们看看所有这些公式。我们点击公式得到这个屏幕:

快速地按类型链接引用摘要,因为它开始加载一个很长的列表,其中包含超过一百万个引用,这可能会杀死你的浏览器。然后我们看到:

这告诉我们两件事:Formula显然是递归数据类型(大多数实例由其他Formula对象引用),其余的由HashMap E n t r y 对 象 引 用 。 第 三 个 地 方 的 公 式 数 组 是 可 以 的 , 因 为 它 包 含 生 成 公 式 的 模 板 ( 我 可 以 通 过 链 接 看 到 这 一 点 , 但 我 没 有 在 这 里 显 示 ) 。 让 我 们 轻 点 H a s h M a p Entry对象引用。第三个地方的公式数组是可以的,因为它包含生成公式的模板(我可以通过链接看到这一点,但我没有在这里显示)。让我们轻点HashMap EntryHashMap条目,看看它会带我们去哪里。它把我们带到这里:

因此,它们要么从数组引用,要么从其他HashMap E n t r y 对 象 引 用 。 G u e s s J a v a 的 哈 希 表 是 通 过 使 用 H a s h M a p Entry对象引用。Guess Java的哈希表是通过使用HashMap EntryGuessJava使HashMapEntry对象的链表来处理冲突的条目数组来实现的。18000次碰撞并不是很好…总之,让我们看看数组:

毫不奇怪,这些都在HashMaps中。以下内容:

这些代码在很多地方都有使用,但是我的代码只在少数地方出现。节点非常有意义:我确实有一些引用节点的图,节点的数量似乎很小,可以理解。SinglePropertyGraph还包含公式,而且对象的数量(1)再次非常合理。不过,DefaultFormulaFactory有点奇怪。为什么它要有一个公式图?而且,它是唯一一个在我的代码执行过程中存在的对象。启动Eclipse并加载工厂:

好像我们找到了罪魁祸首。工厂规范化公式对象。这显然是不好的,当你产生数以百万计。规范化对象的原因是为了允许快速比较,所以我可以使用WeakReferences来解决这个问题,但是我使Formula对象不可变(无法修改),并添加了哈希值的预计算和有效的相等函数:

现在,我刚刚摆脱了规范化,取而代之的是更有效的哈希函数和等式测试。然后我运行我的回归测试,以确保我没有破坏任何东西(回归测试创建起来很糟糕,但当你做这样的事情时,它们真的很震撼)。

验证内存泄漏是否消失

==========

为了验证这是否真的修复了内存泄漏,我再次尝试JConsole,它现在显示了如下内容:

即使随着时间的推移,我们的内存消耗也不再稳定增长。钉子没问题。

为了确保我没有错过任何东西,我再开始我的程序。我让运行几秒钟并创建一个转储(使用jmap),让程序继续运行一两分钟并创建一个辅助转储(到一个新文件)。

然后我们又去查jhat,这次

jhat -J-Xmx2G -baseline heap1.bin heap2.bin

这里heap1.bin是我修复错误后生成的第一个堆映像,heap2.bin是我让程序运行几分钟后创建的映像。-baseline选项告诉jhat使用第一个堆映像作为基线,并将两个映像中的任何对象标记为“old”,将仅在后一个映像中的任何对象标记为“new”。启动浏览器,我们现在选择显示实例计数链接整洁的底部:

这显示了有多少实例以及其中有多少是新实例:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值