39

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

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

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

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

4

6 回答 6

27

字符串有问题

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

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

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

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

您应该关注的是您可以控制的内容,这确保您不会保留比您需要的时间更长的引用,并且您不会不必要地重复事物。Java 中的垃圾收集例程经过高度优化,如果您了解它们的算法是如何工作的,就可以确保您的程序以最佳方式运行,以使这些算法能够正常工作。

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

在其他语言中被认为是内存泄漏的事情/根本原因与 Java 中的垃圾收集系统不同。

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

StringBuffer由于/StringBuilder对象在第一次实例化时大小不合适,因此很可能有很多较小的分配,然后必须自动增长char[]数组以保存后续append()调用。

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

示例:垃圾收集器可能会确定有候选者,但因为它认为还有大量内存可用,所以在那个时间点将它们刷新出来可能过于昂贵,它会等到内存压力越来越大。

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

这些未引用的对象可能只是没有达到垃圾收集器认为需要它们才能从内存中清除它们的时间,或者可能存在对它们的引用由其他对象(List)持有,例如你不实现仍然指向该对象。这就是 Java 中最常见的泄漏,更具体地讲是引用泄漏。

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

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

我没有看到任何提及OutOfMemoryError

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

于 2012-04-11T15:26:42.603 回答
10

JProfiler中,您可以使用 go to the heap walker 并激活最大对象视图。您将看到保留最多内存的对象。“保留”内存是如果您删除了对象,垃圾收集器将释放的内存。

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

在此处输入图像描述

免责声明:我公司开发JProfiler

于 2012-04-15T14:54:49.403 回答
4

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

于 2012-04-11T15:55:21.590 回答
2

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

在此处输入图像描述

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

于 2017-08-01T03:06:40.083 回答
0

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

于 2012-04-11T15:32:48.083 回答
0

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

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

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

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

于 2020-05-08T15:45:42.520 回答