我还在学习Java的诀窍,如果有一个明显的答案,我很抱歉。我有一个程序占用了大量的内存,我想找到一种方法来减少它的使用,但在阅读了许多问题后,我有一个想法,我需要证明问题在哪里,然后再开始优化它。
下面是我所做的,我在程序的开头添加了一个断点并运行它,然后启动visualVM并让它分析内存(我也在netbeans中做了同样的事情,只是为了比较结果,结果是一样的)。我的问题是我不知道如何阅读它们,我得到了最高的区域,显示为char[]
,但看不到任何代码或其他内容(这是有意义的,因为visualvm连接到jvm,看不到我的源代码,但是netbeans也不像在做cpu分析时那样显示源代码)。
基本上我想知道的是哪个变量(希望还有更多细节,比如在哪个方法中)所有的内存都被使用了,这样我就可以专注于那里的工作。有简单的方法可以做到这一点吗?我现在正在使用eclipse和java进行开发(安装了visualVM和netbeans专门用于分析,但我愿意安装任何其他你觉得可以完成这项工作的东西)。
编辑:理想的情况下,我会寻找一个可以接收所有对象并按大小排序的东西(这样我就可以看到哪个对象占用了内存)。目前它返回的是string[]或int[]等通用信息,但我想知道它引用的是哪个对象,这样我就可以进一步优化它的大小。
6条答案
按热度按时间lnlaulya1#
字符串有问题
基本上在Java中,
String
引用(在幕后使用char[]
的东西)将主导大多数 * 业务 * 应用程序的内存,它们是如何创建的决定了它们在JVM中消耗多少内存。正因为
String
数据类型是大多数业务应用程序的基础数据类型,而且它们也是最需要内存的数据类型之一,这不仅仅是Java的问题,在几乎所有语言和运行时库中,String
数据类型都会占用大量内存。因为至少它们只是每个字符1个字节的数组,或者更糟的是(Unicode),它们是每个字符多个字节的数组。有一次,在一个也有Oracle JDBC依赖项的Web应用上分析CPU使用情况时,我发现
StringBuffer.append()
比所有其他方法调用组合占CPU周期的许多数量级,更不用说任何其他单个方法调用了。JDBC驱动程序执行了大量的String
操作,这是一种使用PreparedStatements
处理所有内容的权衡。你所关心的事你无法控制,至少无法直接控制
你应该关注的是你的控制范围,也就是确保你不会在你需要的时间之外占用引用,并且你不会重复不必要的东西。Java中的垃圾收集例程是高度优化的,如果你了解了它们的算法是如何工作的,你就可以确保你的程序以最佳的方式运行,以使这些算法工作。
Java堆内存与其他语言中手动管理的内存不同,这些规则不适用
在其他语言中被认为是“内存泄漏”的东西与Java中的垃圾收集系统是不同的。
最有可能的是,在Java中,内存不是被一个正在泄漏的超级对象(在其他环境中是悬空引用)消耗的。
这很可能是由于
StringBuffer
/StringBuilder
对象在第一次示例化时大小不合适,然后必须自动增长char[]
数组以容纳后续的append()
调用而导致的大量较小分配。由于这些中间对象所处的作用域以及许多其他在运行时可能发生变化的因素,垃圾收集器保留这些中间对象的时间可能比预期的要长。
**示例:**垃圾回收器可能确定存在候选项,但由于它认为仍有大量内存需要使用,因此在该时间点刷新这些候选项可能会耗费大量时间,它将等到内存压力变大。
垃圾收集器现在真的很好,但它不是魔术,如果你正在做退化的事情,它会导致它不能最佳地工作。在互联网上有很多文档关于垃圾收集器的所有版本的JVM设置。
这些未被引用的对象可能只是没有达到垃圾收集器认为需要它们从内存中删除的时间,或者可能有其他对象(
List
)持有的对它们的引用,例如您没有意识到仍然指向该对象。这就是Java中最常见的 leak,更具体地说是引用泄漏。**示例:**如果您知道需要使用
StringBuilder
构建一个4KString
,请使用new StringBuilder(4096);
创建它,而不是默认值,这类似于32,并将立即开始创建垃圾,垃圾可能代表您认为对象大小应该是的许多倍。你可以发现有多少什么类型的对象是用VisualVM示例化的,这将告诉你你需要知道的。不会有一个大的闪光灯指向一个类的一个示例说,“这是大内存消耗!",也就是说,除非你正在阅读一些大规模文件的某个
char[]
的只有一个示例,这也是不可能的,因为很多其他类在内部使用char[]
;你应该早就知道了。我没有看到任何关于
OutOfMemoryError
的内容你的代码中可能没有问题,垃圾收集系统可能只是没有受到足够的压力来启动和释放你认为应该清理的对象。你认为的问题可能不是,除非你的程序因为
OutOfMemoryError
而崩溃。这不是C,C++,Objective-C,或者任何其他手动内存管理语言/运行时。你不能决定什么在内存中,或者不在你期望的细节级别上。bn31dyow2#
在JProfiler中,你可以进入heap步行者并激活bigest objects视图,你会看到那些保留了大部分内存的对象。“Retained”内存是指如果你删除了对象,垃圾收集器会释放的内存。
然后,您可以打开对象节点来查看所保留对象的引用树。以下是最大对象视图的屏幕截图:
免责声明:我的公司开发JProfiler
k4aesqcs3#
我建议捕获堆转储并使用Eclipse MAT这样的工具来分析它们。有许多tutorials可用。它提供了dominator tree的视图,以便深入了解堆上对象之间的关系。特别是对于您提到的内容,MAT的“路径到GC根”特性将告诉您这些char[]、String[]和int[]对象正在被引用。JVisualVM在识别泄漏和分配方面也很有用,特别是通过使用带有分配堆栈跟踪的快照。获取快照并比较它们以找到分配点有很多walk-throughs of the process。
e7arh2l64#
Java JDK在bin文件夹下附带了JVisualVM,一旦您的应用服务器(例如正在运行),您就可以运行visualvm并将其连接到本地主机,这将为您提供内存分配并使您能够执行堆转储
0qx6xfy65#
如果你使用visualVM来检查你的内存使用情况,它关注的是数据,而不是方法。也许你的大char[]数据是由许多String值引起的?除非你使用递归,否则数据不会来自局部变量。所以你可以关注那些将元素插入到大数据结构中的方法。要找出是什么精确的语句导致你的“内存泄漏”,我建议你另外
h9vpoimq6#
通常有两种不同的方法来分析Java代码,以了解其内存分配概况。如果您试图测量特定的一小段代码的影响,比如您想比较两个替代实现,以确定哪一个提供更好的运行时性能,您将使用微基准测试工具,如JMH。
虽然你可以暂停正在运行的程序,但JVM是一个复杂的运行时,它执行各种各样的内务处理任务,很难获得“时间点”快照和“内存使用水平”的准确阅读。它可能以不能直接反映正在运行的Java程序行为的速率分配/释放内存。类似地,执行Java对象堆转储并不完全捕获指示实际存储器占用的低级机器专用存储器布局,因为这可能取决于机器体系结构、JVM版本和其它运行时因素。
像JMH这样的工具通过重复运行一小段代码,并观察多次调用的内存分配的长期平均值来解决这个问题。例如,在GC profiling sample JMH benchmark中,派生的
*·gc.alloc.rate.norm
度量给出了相当准确的每次调用的标准化内存成本。在更一般的情况下,您可以将Profiler附加到正在运行的应用程序并获取JVM级别的度量,或者执行堆转储以进行离线分析。一些用于分析整个应用程序的常用工具是Async Profiler和新开源的Java Flight Recorder in conjunction with Java Mission Control,以可视化结果。