作为 Java 开发人员,我们熟悉我们的应用程序抛出 OutOfMemoryErrors 或我们的服务器监控工具抛出警报并抱怨 JVM 内存利用率高。

要调查内存问题,通常首先要查看 JVM 堆内存。

要进行此操作,我们可以先触发程序抛出 OutOfMemoryError,然后捕获堆转储。接下来我们将分析这个堆转储,以确定可能导致内存泄漏的潜在对象。

什么是堆转储(Heap Dumps)?

每当我们通过创建类的实例来创建 Java 对象时,它总是放置在称为堆的区域中。 Java 运行时的类也在这个堆中创建。

JVM 启动时会创建堆。它在运行时扩展或收缩以适应在我们的应用程序中创建或销毁的对象。

当堆满时,垃圾收集过程将运行以收集不再被引用的对象(即程序不再使用它们)。

堆转储包含Java应用程序当前正在使用的一些存活对象实例(注意:在堆内存中的存活对象)的快照。我们可以获得每个对象实例的详细信息,例如地址、类型、类名或大小,以及该实例是否有其他对象的引用。

堆转储有两种格式:

  • 经典格式(the classic format)
  • 便携式堆转储 (PHD) 格式(the Portable Heap Dump (PHD) format)

PHD 是默认格式。经典格式是人类可读的,因为它是 ASCII 文本,但 PHD 格式是二进制的,应通过适当的工具进行处理以进行分析。

生成 OutOfMemoryError 的示例程序

为了解释堆转储的分析,我们将使用一个简单的 Java 程序来生成 OutOfMemoryError:

public class OOMGenerator {/*** @param args* @throws Exception */public static void main(String[] args) throws Exception {System.out.println("Max JVM memory: " + Runtime.getRuntime().maxMemory());try {ProductManager productManager = new ProductManager();productManager.populateProducts();} catch (OutOfMemoryError outofMemory) {System.out.println("Catching out of memory error");throw outofMemory;}}
}
public class ProductManager {private static ProductGroup regularItems = new ProductGroup();private static ProductGroup discountedItems = new ProductGroup();public void populateProducts() {int dummyArraySize = 1;for (int loop = 0; loop < Integer.MAX_VALUE; loop++) {if(loop%2 == 0) {createObjects(regularItems, dummyArraySize);}else {createObjects(discountedItems, dummyArraySize);}System.out.println("Memory Consumed till now: " + loop + "::"+ regularItems + " "+discountedItems );dummyArraySize *= dummyArraySize * 2;}}private void createObjects(ProductGroup productGroup, int dummyArraySize) {for (int i = 0; i < dummyArraySize; ) {productGroup.add(createProduct());}}private AbstractProduct createProduct() {int randomIndex = (int) Math.round(Math.random() * 10);switch (randomIndex) {case 0:return  new ElectronicGood();case 1:return  new BrandedProduct();case 2:return new GroceryProduct();case 3:return new LuxuryGood();default:return  new BrandedProduct();}}}

我们通过运行 for 循环持续分配内存,直到到达某个点,当 JVM 没有足够的内存来分配时,导致抛出 OutOfMemoryError。

查找 OutOfMemoryError 的根本原因

我们现在将通过堆转储分析来找出此错误的原因。这分两步完成:

  • 捕获堆转储
  • 分析堆转储文件,定位可疑原因。

我们可以通过多种方式捕获堆转储。让我们首先使用 jmap 捕获我们示例的堆转储,然后在命令行中传递一个 VM 参数。

使用 jmap 按需生成堆转储

jmap工具 与 JDK 打包在一起,并将堆转储提取到指定的文件位置。

要使用 jmap 生成堆转储,我们首先使用 jps 工具找到我们正在运行的 Java 程序的进程 ID,以列出我们机器上所有正在运行的 Java 进程:

运行 jps 命令后,我们可以看到进程以“ ”格式列出。

接下来,我们运行 jmap 命令来生成堆转储文件:

jmap -dump:live,file=mydump.hprof 41927

运行此命令后,中麦网将创建扩展名为 hprof 的堆转储文件。

选项 -dump:live 用于仅收集在运行代码中仍有引用的活动对象。使用 live 选项时,会触发完整的 GC 以清除无法访问的对象,然后仅转储有引用的活动对象。

在 OutOfMemoryErrors 上自动生成堆转储

此选项用于在发生 OutOfMemoryError 时自动捕获堆转储。这有助于诊断问题,因为我们可以看到哪些对象位于内存中,以及它们在 OutOfMemoryError 发生时占用的内存百分比。

我们将在我们的示例中使用此选项,因为它可以让我们更深入地了解崩溃的原因。

让我们从命令行或我们最喜欢的 IDE 使用 VM 选项 HeapDumpOnOutOfMemoryError 运行程序以生成堆转储文件:

java -jar target/oomegen-0.0.1-SNAPSHOT.jar \-XX:+HeapDumpOnOutOfMemoryError \-XX:HeapDumpPath=hdump.hprof

使用这些 VM 参数运行我们的 Java 程序后,我们得到以下输出:

Max JVM memory: 2147483648Memory Consumed till now: 960Memory Consumed till now: 29760Memory Consumed till now: 25949760java.lang.OutOfMemoryError: Java heap spaceDumping heap to /hdump.hprof …Heap dump file created [17734610 bytes in 0.031 secs]Catching out of memory errorException in thread "main" java.lang.OutOfMemoryError: Java heap spaceat io.pratik.OOMGenerator.main(OOMGenerator.java:25)

从输出中可以看出,当 OutOfMemoryError 发生时,会创建名为 hdump.hprof 的堆转储文件。

生成堆转储的其他方法

生成堆转储的其他一些方法是:

  1. jcmd:jcmd 用于向JVM 发送诊断命令请求。它被打包为 JDK 的一部分。它可以在 Java 安装的 \bin 文件夹中找到。
  2. JVisualVM:通常,分析堆转储需要比实际堆转储大小更多的内存。如果我们试图在开发机器上分析来自大型服务器的堆转储,这可能会出现问题。 JVisualVM 提供了堆内存的实时采样,因此它不会占用整个内存。

分析堆转储(Heap Dump)

我们在堆转储中寻找的是:

  • 内存使用率高的对象
  • 用于识别未释放内存的对象的对象图
  • 可达和不可达对象

Eclipse Memory Analyzer (MAT) 是分析 Java 堆转储的最佳工具之一。让我们通过分析我们之前生成的堆转储文件来了解使用 MAT 进行 Java 堆转储分析的基本概念。

我们将首先启动内存分析器工具并打开堆转储文件。在 Eclipse MAT 中,报告了两种类型的对象大小:

  • 浅堆大小(Shallow heap size):对象的浅堆是它在内存中的大小
  • 保留堆大小(Retained heap size):保留堆是对象被垃圾回收时将释放的内存量。

MAT 中的概述部分

打开堆转储后,我们将看到应用程序内存使用情况的概览。饼图在概览选项卡中按保留大小显示最大的对象,如下所示:

对于我们的应用程序,概述中的这些信息意味着如果我们可以处理 java.lang.Thread 的特定实例,我们将节省 1.7 GB,以及该应用程序中使用的几乎所有内存。

直方图视图

虽然这看起来很有希望,但 java.lang.Thread 不太可能是这里的真正问题。为了更好地了解当前存在哪些对象,我们将使用直方图视图:

我们使用正则表达式“io.pratik.*”过滤了直方图,以仅显示与模式匹配的类。通过此视图,我们可以看到活动对象的数量:例如,系统中有 243 个 BrandedProduct 对象和 309 个Price对象。我们还可以看到每个对象使用的内存量。

有两种计算,浅堆(Shallow heap)和保留堆(Retained heap)。浅堆是一个对象消耗的内存量。对于每个引用,对象需要 32(或 64 位,取决于体系结构)。整数和长整型等基元需要 4 或 8 个字节,等等……虽然这可能很有趣,但更有用的指标是保留堆。

保留堆大小(Retained Heap Size)

保留堆大小是通过将保留集中所有对象的大小相加来计算的。保留的 X 集是垃圾收集器在收集 X 时将删除的对象集。

保留堆可以通过两种不同的方式计算,使用快速近似或精确保留大小:

通过计算保留堆,我们现在可以看到 io.pratik.ProductGroup 占据了大部分内存,即使它本身只有 32 字节(浅堆大小)。通过找到释放这个对象的方法,我们当然可以控制我们的内存问题。

支配树(Dominator Tree)

支配树用于标识保留的堆。它由运行时生成的复杂对象图生成,有助于识别最大的内存图。如果从根到 Y 的每条路径都必须经过 X,则称对象 X 支配对象 Y。

查看我们示例的支配树,我们可以看到哪些对象保留在内存中。

我们可以看到 ProductGroup 对象持有内存而不是 Thread 对象。我们或许可以通过释放这个对象中包含的对象来解决内存问题。

泄漏嫌疑报告(Leak Suspects Report)

我们还可以生成“泄漏嫌疑报告”以查找疑似大对象或对象集。此报告在 HTML 页面上显示调查结果,并且还保存在堆转储文件旁边的 zip 文件中。

由于其较小,最好与专门执行分析任务的团队共享“泄漏可疑报告”报告,而不是原始堆转储文件。

该报告有一个饼图,其中给出了可疑对象的大小:

对于我们的示例,我们标记了一个嫌疑问题,并用简短描述进一步描述:

除摘要外,本报告还包含有关嫌疑问题的详细信息,可通过报告底部的“详细信息”链接访问:

详细信息包括:

  1. 从GC根到累积点的最短路径:在这里我们可以看到引用链所经过的所有类和字段,这很好地理解了对象是如何保持的。在此报告中,我们可以看到从 Thread 到 ProductGroup 对象的引用链。
  2. 支配树中的累积对象:这提供了一些关于累积内容的信息,这些内容是此处的 GroceryProduct 对象的集合。

总结

在这篇文章中,我们介绍了堆转储,它是 Java 应用程序运行时对象内存图的快照。为了说明这一点,我们从一个在运行时抛出 OutOfMemoryError 的程序中捕获了堆转储。

然后我们查看了使用 Eclipse Memory Analyzer 进行堆转储分析的一些基本概念:大对象、GC 根、浅堆与保留堆以及支配树,所有这些都将帮助我们确定特定内存问题的根本原因。

最后

今天给大家分享一部关于Java性能调优的卓越作品。《Java性能权威指南》该书涉及性能测试、性能分析、性能调优的原理、方法、工具等诸多方面,书中最新的JVM和体系结构的相关知识可以帮助我们更好地理解Java。

同时该书又包含了许多非常工程性的经验,比如多线程、数据库、序列化以及JavaAPI等,这些经验不仅对Java工程师很有帮助,也为其他开发人员及性能调优人员提供了问题解决思路和方法上的启迪。

创建和分析 Java 堆转储(Heap Dumps)相关推荐

  1. java 堆转储快照_Java堆转储:您可以完成任务吗?

    java 堆转储快照 如果您像我一样对Java性能充满热情,那么堆转储分析对您来说应该不是一个谜. 如果是这样,那么好消息是您将有机会增加您的Java故障排除技能和JVM知识. JVM现在已经发展到这 ...

  2. Java堆转储:您可以完成任务吗?

    如果您像我一样对Java性能充满热情,那么堆转储分析对您来说应该不是一个谜. 如果是这样,那么好消息是您将有机会提高您的Java故障诊断技能和JVM知识. JVM现已发展到今天,与旧的JDK 1.0 ...

  3. java 堆转储快照_捕获Java堆转储的7个选项

    java 堆转储快照 堆转储是诊断与内存相关的问题的重要工件,例如内存泄漏缓慢,垃圾回收问题和java.lang.OutOfMemoryError.它们也是优化内存消耗的重要工件. 有很棒的工具,例如 ...

  4. Java堆空间(Heap Space)

    Java 堆空间(Heap Space) 概述 在Java程序中,堆是JVM内存空间中最大的一块,同时我们知道,每个线程都拥有一个虚拟机栈,但是堆不同,Java堆是被所有线程共享的一块内存区域,在虚拟 ...

  5. Java 堆空间(Heap Space)

    Java 堆空间(Heap Space) 概述 在Java程序中,堆是JVM内存空间中最大的一块,同时我们知道,每个线程都拥有一个虚拟机栈,但是堆不同,Java堆是被所有线程共享的一块内存区域,在虚拟 ...

  6. java 转储快照分析_分析Java核心转储

    java 转储快照分析 在本文中,我将向您展示如何调试Java核心文件,以查看导致JVM崩溃的原因. 我将使用在上一篇文章: 生成Java Core Dump中生成的核心文件. 您可以通过以下几种方法 ...

  7. Java堆转储文件的生成及工具分析

    因Java堆是Java对象在内存中使用的主要内存空间,当发生内存溢出或泄漏时,保存堆信息是问题产生原因的重要原料及问题分析的基础,而后才能分析追根溯源. 这里记录演示产生内存溢出生成堆转储文件,使用工 ...

  8. Java堆转储Dump文件的几种方法,java高级程序员面试笔试

    jmap -dump:live,format=b,file=/tmp/dump.hprof 12587 我们可以通过使用jps命令轻松获得Java进程的pid. 请记住,jmap是作为实验工具引入JD ...

  9. 捕获Java堆转储的7个选项

    堆转储是诊断与内存相关的问题的重要工件,例如内存泄漏缓慢,垃圾回收问题和java.lang.OutOfMemoryError.它们也是优化内存消耗的重要工件. 有很多很棒的工具,例如Eclipse M ...

最新文章

  1. tomcat下面web应用发布路径配置 ( 即虚拟目录配置 )
  2. python pptp链接_渗透技巧——PPTP口令的获取与爆破
  3. 一套代码小程序WebNative运行的探索02
  4. 大型Java项目架构演进
  5. log4j.xml配置文件
  6. java学习(94):cpu随机调用线程测试
  7. [Err] 22007 - [SQL Server]从 nvarchar 数据类型到 datetime 数据类型的转换产生一个超出范围的值。
  8. 单位阶跃信号是周期信号吗_vivoS7e是5G手机吗-支持5G吗-5G信号怎么样
  9. sqlyog设置简体中文_SQLyog中文版使用教程
  10. php字符是汉字还是字符,php判断字符串中是否包含中文汉字和获得字符串中的汉字...
  11. 谁蹭了我的WiFi?浅谈家用无线路由器攻防
  12. 电脑配置PC2022年版(4000元左右)详细配置表——(专业数据)
  13. 这几款火爆的独立游戏告诉你,寒冬?不存在的
  14. Java 数学三角函数正弦、余弦、正切以及反正弦、反余弦、反正切函数的使用
  15. Selenium使用自带浏览器自动化
  16. 传智博客JAVA基础第二十三天
  17. HashMap初始容量指定规则
  18. SEVERE: Could not contact [localhost:8005] (base port [8005] and offset [0]). Tomcat may not be runn
  19. MYSQL安装丢失MSVCR120.dll问题
  20. 性能测试指南 | 一些实用的排查命令(未完待续)

热门文章

  1. uTorrent端口设置
  2. linux端口开放设置
  3. 阿里云服务器开放端口设置(超详细)
  4. Ext JS 6.7 中文文档:路由的使用
  5. python培训感悟,Python培训知识总结系列
  6. 如何在手游中实现云渲染的效果?
  7. 关于java 上传的音频或视频文件获取时长及视频封面
  8. 360 度评估中的提问示范
  9. Liferay7开发系列(四)Portlet
  10. 记我的第一个APP-简易版飞花令