【问题标题】:How to memory profile in Java?如何在 Java 中进行内存配置文件?
【发布时间】:2012-04-23 22:26:52
【问题描述】:

如果有一个明显的答案,我仍在学习 Java 的绳索,很抱歉。我有一个占用大量内存的程序,我想找到一种方法来减少它的使用,但是在阅读了许多 SO 问题后,我认为我需要在开始优化它之前证明问题出在哪里。

这就是我所做的,我在程序的开头添加了一个断点并运行它,然后我启动了 visualVM 并让它分析内存(我也在 netbeans 中做了同样的事情,只是为了比较结果和它们是相同的)。我的问题是我不知道如何阅读它们,我得到的最高区域只是说 char[] 并且我看不到任何代码或任何东西(这是有道理的,因为 visualvm 正在连接到 jvm 并且看不到我的源,但 netbeans 也不会像在进行 cpu 分析时那样向我显示源)。

基本上,我想知道的是所有内存都在使用哪个变量(希望有更多详细信息,例如在哪种方法中),这样我就可以专注于在那里工作。有没有简单的方法可以做到这一点?我现在正在使用 eclipse 和 java 进行开发(并专门安装了用于分析的 visualVM 和 netbeans,但我愿意安装您认为可以完成这项工作的任何其他东西)。

编辑:理想情况下,我正在寻找可以获取所有对象并按大小对它们进行排序的东西(这样我就可以看到哪个对象占用了内存)。目前它返回一般信息,如 string[] 或 int[] 但我想知道它指的是哪个对象,以便我可以进一步优化它的大小。

【问题讨论】:

  • 我使用了一个分析器,它也向我展示了对象被分配的位置。我不知道 VisualVM 是否可以做到这一点,但它非常有用。我使用的是YourKit,但它不是免费的(但你可以获得评估许可证)
  • @PeterLawrey 我实际上阅读了您不久前写的一个答案,您提到 yourkit 是您使用的首选,我确实查了一下(因为您的建议总是很棒)但是是的,它有点贵我只是在学习。
  • 如果不使用检测您的代码的东西,任何分析器都无法指出哪个实例 == 您在源代码中称为引用。

标签: java profiling visualvm


【解决方案1】:

字符串有问题

基本上在 Java 中,String 引用(在幕后使用 char[] 的东西)将主导大多数业务应用程序的内存。它们的创建方式决定了它们在 JVM 中消耗了多少内存。

仅仅因为它们作为一种数据类型对于大多数业务应用程序来说非常重要,而且它们也是最需要内存的类型之一。这不仅仅是 Java 的事情,String 数据类型在几乎每种语言和运行时库中都会占用大量内存,因为至少它们只是每个字符 1 个字节的数组,或者更糟糕的是(Unicode)它们是每个字符多个字节的数组。

有一次,在对同样具有 Oracle JDBC 依赖项的 Web 应用程序的 CPU 使用情况进行分析时,我发现 StringBuffer.append() 在 CPU 周期中占主导地位,比所有其他方法调用组合高出许多数量级,更不用说任何其他单个方法调用。 JDBC 驱动程序做了很多很多 String 操作,有点像使用 PreparedStatements 做所有事情的权衡。

你所关心的你无法控制,反正也不能直接控制

您应该关注的是在您的控制范围内的内容,即确保您不会在需要的时间内持有参考资料,并且您不会不必要地重复某些内容。 Java 中的垃圾收集例程经过高度优化,如果您了解它们的算法是如何工作的,就可以确保您的程序以最佳方式运行以使这些算法正常工作。

Java 堆内存不像其他语言中的手动管理内存,这些规则不适用

在其他语言中被视为内存泄漏的东西与 Java 中的垃圾回收系统不同。

在 Java 中,内存很可能不会被一个正在泄漏的 uber-object 消耗(其他环境中的悬空引用)。

这很可能是因为StringBuffer/StringBuilder 对象在第一次实例化时大小不合适,然后不得不自动增长char[] 数组以容纳后续append() 调用,因此很可能是大量较小的分配。

由于它们所处的范围以及在运行时可能发生变化的许多其他因素,这些中间对象的保留时间可能比垃圾收集器预期的要长。

示例: 垃圾收集器可能会确定有候选者,但因为它认为仍有大量内存可用,因此在时间上清除它们可能过于昂贵时间点,等到内存压力变大。

垃圾收集器现在真的很好,但它不是魔法,如果你在做退化的事情,它会导致它不能最佳地工作。互联网上有很多关于所有 JVM 版本的垃圾收集器设置的文档。

这些未引用的对象可能只是没有达到垃圾收集器认为需要它们才能从内存中清除它们的时间,或者可能有对它们的引用被其他对象(List)持有您没有意识到的示例仍然指向该对象。这就是 Java 中最常被称为 leak 的东西,更具体地讲是引用泄漏。

示例:如果您知道需要使用 StringBuilder 构建 4K String,请使用 new StringBuilder(4096); 而非默认值(例如 32)创建它,并将立即开始创建垃圾可以多次表示您认为对象的大小。

您可以发现使用 VisualVM 实例化了多少类型的对象,这将告诉您需要了解的内容。不会有一个大闪光灯指向单个类的单个实例,它说“这是大内存消耗者!”,除非您只有一个 char[] 的实例读取一些大文件,这也是不可能的,因为许多其他类在内部使用char[];然后你几乎已经知道了。

我没有看到任何提及OutOfMemoryError

您的代码可能没有问题,垃圾收集系统可能没有承受足够的压力来启动和释放您认为应该清理的对象。 您认为可能不是问题,除非您的程序因OutOfMemoryError 而崩溃。这不是 C、C++、Objective-C 或任何其他手动内存管理语言/运行时。您无法以您期望的详细程度来决定内存中的内容。

【讨论】:

    【解决方案2】:

    JProfiler 中,您可以转到堆遍历器并激活最大对象视图。您将看到保留最多内存的对象。 “保留”内存是指删除对象后垃圾收集器将释放的内存。

    然后您可以打开对象节点以查看保留对象的引用树。这是最大对象视图的屏幕截图:

    免责声明:我公司开发JProfiler

    【讨论】:

      【解决方案3】:

      我建议捕获堆转储并使用像 Eclipse MAT 这样的工具来分析它们。有很多tutorials 可用。它提供了dominator tree 的视图,以深入了解堆上对象之间的关系。特别是对于您提到的内容,MAT 的“GC 根路径”功能将告诉您这些 char[]、String[] 和 int[] 对象中的大多数被引用到哪里。 JVisualVM 在识别泄漏和分配方面也很有用,特别是通过使用带有分配堆栈跟踪的快照。有不少walk-throughs of the process 获取快照并比较它们以找到分配点。

      【讨论】:

        【解决方案4】:

        Java JDK 在 bin 文件夹下带有 JVisualVM,一旦您的应用服务器(例如正在运行),您就可以运行 visualvm 并将其连接到您的本地主机,这将为您提供内存分配并使您能够执行堆转储

        有关如何启用的更详细步骤: http://sysdotoutdotprint.com/technologies/java/6

        【讨论】:

          【解决方案5】:

          如果您使用 visualVM 检查内存使用情况,它会关注数据,而不是方法。也许您的大 char[] 数据是由许多 String 值引起的?除非您使用递归,否则数据不会来自局部变量。因此,您可以专注于将元素插入大型数据结构的方法。要找出导致您“内存泄漏”的确切语句,我建议您另外

          【讨论】:

            【解决方案6】:

            通常有两种不同的方法来分析 Java 代码以了解其内存分配配置文件。如果您尝试测量特定的一小段代码的影响——假设您想比较两个替代实现以确定哪个实现更好的运行时性能——您可以使用微基准测试工具,例如 JMH

            虽然您可以暂停正在运行的程序,但 JVM 是一个复杂的运行时,它执行各种内务处理任务,并且很难获得“时间点”快照和准确读取“内存使用水平”。它可能以不直接反映正在运行的 Java 程序的行为的速率分配/释放内存。同样,执行 Java 对象堆转储并不能完全捕获指示实际内存占用的低级机器特定内存布局,因为这可能取决于机器架构、JVM 版本和其他运行时因素。

            像 JMH 这样的工具通过重复运行一小段代码并观察多次调用的内存分配的长期平均值来解决这个问题。例如。在GC profiling sample JMH benchmark 中,派生的*·gc.alloc.rate.norm 指标给出了相当准确的每次调用标准化内存成本。

            在更一般的情况下,您可以将分析器附加到正在运行的应用程序并获取 JVM 级别的指标,或者执行堆转储以进行离线分析。用于分析完整应用程序的一些常用工具是 Async Profiler 和新开源的 Java Flight Recorder in conjunction with Java Mission Control 以可视化结果。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-08-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-03-26
              相关资源
              最近更新 更多