java内存泄漏分析工具linux(Java 内存泄露的构造和检测)java教程 / Java内存泄漏检测与解决...

wufei123 发布于 2024-05-17 阅读(7)

1. 概述在 Java 应用程序中,内存泄漏会导致严重的性能下降和系统故障开发人员必须了解内存泄漏的发生原因以及如何识别和解决它们在本教程中,我们将提供一个使用失效的监听器问题作为示例来创建 Java 内存泄漏的指南。

我们还将讨论各种检测内存泄漏的方法,包括日志记录、分析、详细垃圾回收和堆转储2. 构造内存泄漏我们将考虑失效的监听器问题作为内存泄漏的示例这是学习Java中内存分配和垃圾回收的一个很好的方式让我们创建一个应用程序,向已登录并订阅我们的服务的用户发送随机电影名言。

这个应用程序非常简单,一次只能为一个用户提供服务:scss复制代码publicstaticvoidmain(String[] args){ while (true) { User user = generateUser(); logger.debug(

"{} logged in", user.getName()); user.subscribe(movieQuoteService); userUsingService(); logger.debug(

"{} logged out", user.getName()); } } 123456789_UserGenerator _是一个简单的类,提供无限的随机用户我们将使用 Datafaker 进行随机化:。

ini复制代码publicclass UserGenerator { private final static Faker faker = new Faker(); publicstatic

User generateUser() { System.out.println("Generating user"); String name = faker.name().fullName();

String email = faker.internet().emailAddress(); String phone = faker.phoneNumber().cellPhone();

String street = faker.address().streetAddress(); String city = faker.address().city();

String state = faker.address().state(); String zipCode = faker.address().zipCode(); return

new User(name, email, phone, street, city, state, zipCode); } } 12345678910111213141516用户与我们的服务之间的关系将基于观察者模式。

因此,_Users _可以订阅服务,我们的 MovieQuoteService 将向用户更新新的电影名言此示例的主要问题是,_Users _从未从服务中取消订阅 这会导致内存泄漏,即使用户超出了范围,也不能通过垃圾收集器删除它们,因为服务保留了它们的引用。

我们可以明确取消订阅用户来减轻此问题,这将起作用但是,最好的解决方案是使用 WeakReferences 来自动化此过程3. 检测内存泄漏在上一节中,我们创建了一个存在严重问题的应用程序——内存泄漏尽管这个问题可能是灾难性的,但通常很难检测到。

3.1. 日志记录让我们从最简单的方法开始,使用日志记录来查找系统中的问题这不是检测内存泄漏的最高级方法,但它易于使用,可能有助于发现异常在运行我们的服务时,日志输出会显示用户活动:ini复制代码21:58:24.280。

[pool-1-thread-1]DEBUG c.b.lapsedlistener.MovieQuoteService - New quote:Goahead,makemyday.21:58:24.358

[main]DEBUGc.b.l.LapsedListenerRunner-EarlRunolfsdottirloggedin21:58:24.358[main]DEBUG c.b.lapsedlistener.MovieQuoteService - Current number of subscribed users:

021:58:24.371[main]DEBUGc.b.l.LapsedListenerRunner-EarlRunolfsdottirloggedout21:58:24.372[main]DEBUGc.b.l.LapsedListenerRunner

-BarbraRosenbaumloggedin21:58:24.372[main]DEBUG c.b.lapsedlistener.MovieQuoteService - Current number of subscribed users:

121:58:24.383[main]DEBUGc.b.l.LapsedListenerRunner-BarbraRosenbaumloggedout21:58:24.383[main]DEBUGc.b.l.LapsedListenerRunner

-LeighannMcCulloughloggedin21:58:24.383[main]DEBUG c.b.lapsedlistener.MovieQuoteService - Current number of subscribed users:

221:58:24.396[main]DEBUGc.b.l.LapsedListenerRunner-LeighannMcCulloughloggedout21:58:24.397[main]DEBUG

c.b.l.LapsedListenerRunner-Mr.CharlieKeelingloggedin21:58:24.397[main]DEBUG c.b.lapsedlistener.MovieQuoteService - Current number of subscribed users:

321:58:24.409[main]DEBUGc.b.l.LapsedListenerRunner-Mr.CharlieKeelingloggedout21:58:24.410[main]DEBUGc.b.l.LapsedListenerRunner

-AlvinOConnellloggedin21:58:24.410[main]DEBUG c.b.lapsedlistener.MovieQuoteService - Current number of subscribed users:

421:58:24.423[main]DEBUGc.b.l.LapsedListenerRunner-AlvinOConnellloggedout21:58:24.423[main]DEBUGc.b.l.LapsedListenerRunner

-TraceyStoltenbergloggedin21:58:24.423[main]DEBUG c.b.lapsedlistener.MovieQuoteService - Current number of subscribed users:

5123456789101112131415161718我们可以在上面的片段中注意到一个有趣的事情如前所述,我们的应用程序一次只能处理一个用户因此,只有一个用户可以订阅我们的服务与此同时,日志显示订阅者的数量超过了这个值。

进一步阅读提供了更多证据,证明我们的系统存在问题尽管日志没有显示问题发生的地方,但这是防止系统出现问题的第一步3.2. 性能分析与前一个步骤一样,此步骤旨在找到正在运行的应用程序中的异常然而,性能分析器 可以显著简化对正在运行的应用程序的内存占用的监控。

首先要注意的是,随着时间的推移,使用的内存单调增加 这并不总是内存泄漏的标志然而,在像我们这样的应用程序上,内存使用的增加可能是我们有问题的一个很好的迹象我们将使用 JConsole 分析器这是一个基本的分析器,但它提供了所有所需的功能,并包含在每个 JDK 分发中。

另外,它很容易在任何系统上启动:ruby复制代码$ jconsole 1让我们启动应用程序,看看 JConsole 会告诉我们什么。在启动应用程序后,其内存消耗增加:

然而,内存使用并不总是内存泄漏的迹象。让我们尝试提示垃圾收集器清理一些死亡对象:

如我们所见,垃圾收集器工作得相当好,清理了一些空间因此,我们可以假设我们根本没有任何问题然而,让我们看看 Old Generation这是应用程序中一些对象经过几次垃圾回收后依然存在的空间我们可以看到它的大小不断增加:。

一个解释是,除了用户之外,我们还有引用我们的应用程序中没有存储引用的引用,所以垃圾收集器在清理它们时没有问题与此同时,我们的服务保留了对每个用户的引用,阻止它们被垃圾收集,并将它们提升到 Old Generation:。

尽管垃圾收集器定期清理,但很明显,总体内存消耗随着时间的推移在增长我们在几分钟内从大约 10 MB 增加到了 30 MB 在服务器上,这可能几个小时甚至几天都不会造成任何问题如果服务器定期重启,我们可能永远不会看到 。

OutOfMemoryError:

我们在 old generation 中也有同样的情况:内存消耗只是在增长对于我们这样的应用程序,一次只能为一个用户提供服务,这是一个问题的迹象3.3. 查看详细垃圾回收日志这是另一种检查堆状态和垃圾回收过程的方法。

根据 Java 版本,我们可以使用一些标志来开启详细垃圾回收日志输出将反映我们在 JConsole 中获得的先前信息:scss复制代码[0.004s][info][gc] Using G1 [0.210s

][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 23M->6M(392M) 1.693ms [33.169s][info][gc

] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 38M->7M(392M) 1.994ms [250.890s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 203M->16M(392M) 11.420ms [

507.259s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 228M->25M(392M) 14.321ms [786.181s

][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 229M->33M(392M) 17.410ms [1073.277s][info

][gc] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 241M->41M(392M) 11.251ms [1341.717s][info][gc] GC(6) Pause Young (Normal) (G1 Evacuation Pause) 241M->48M(392M) 17.132ms 123456789

这些日志使用特定格式来显示随着时间的推移,整体内存消耗增加这是检查应用程序的内存占用并查找问题的快速简便方法然而,在这一步之后,我们需要找到这个问题的原因在我们只有几个类的应用程序中,任务可能是微不足道的,我们可以通过审查我们的代码来解决它。

与此同时,在一个庞大的应用程序中,仅通过查看代码可能无法检测到问题3.4. 堆转储有几种方法可以捕获堆转储,JDK 包括几个控制台工具。我们将使用 VisualVM 来捕获和阅读堆转储:

这是一个方便的工具,可以捕获堆转储,并包含 JConsole_的所有功能,使过程变得非常简单在捕获堆转储后,我们可以回顾并分析它在我们的例子中,我们将尝试找到不应该存在的活动对象幸运的是,VisualVM 为堆转储生成了一个概要,显示了重要的信息:。

在我们的系统中,用户在实例数量和整体大小方面排名第三。我们已经知道我们有一个内存消耗问题,现在我们找到了罪魁祸首。此外,VisualVM 还允许我们更详细地分析堆转储,并检查堆中的所有实例:

这在具有复杂对象交互的大型应用程序中可能非常有帮助此外,这对于调整应用程序和找到问题区域可能也很有用在找到问题实例后,我们仍然需要检查代码以查看内存泄漏何时出现,但现在我们可以缩小搜索范围4. 结论内存泄漏会对 Java 应用程序产生重大影响,导致内存逐渐耗尽和潜在的系统故障。

在本教程中,我们为教学目的创建了一个内存泄漏,并讨论了各种检测技术,包括日志记录、分析、查看详细垃圾回收和堆转储每种方法都可以提供有关应用程序运行时行为和内存消耗的有价值的见解日志记录有助于识别异常,而分析和详细垃圾回收日志监视内存使用情况和垃圾回收过程。

堆转储可以识别出问题对象及其引用,缩小内存泄漏的来源了解 Java 中的内存分配和垃圾回收有助于开发人员防止内存泄漏并构建更高效、健壮的应用程序。 与往常一样,源代码可以在 GitHub 上找到。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

大众 新闻28659