java应用程序占用高内存_对Java应用程序中的内存问题进行故障排除

java应用程序占用高内存

重要要点

  • 解决内存问题可能很棘手,但是正确的方法和正确的工具集可以大大简化此过程。
  • Java HotSpot JVM可以报告几种OutOfMemoryError消息,因此务必清楚地理解这些错误消息,并且在我们的工具箱中具有广泛的诊断和故障排除工具以诊断和根除这些问题,这一点很重要。
  • 在本文中,我们介绍了广泛的诊断工具,这些工具在解决内存问题方面非常有用,包括:
    • HeapDumpOnOutOfMemoryError和PrintClassHistogram JVM选项
    • Eclipse垫
    • Java VisualVM
    • J控制台
    • 贾特
    • YourKit
    • 映射
    • jcmd
    • Java Flight Recorder和Java Mission Control
    • GC日志
    • NMT
    • 本机内存泄漏检测工具,例如dbx,libumem,valgrind,purify等。

对于Java进程,有几个内存池或空间-Java堆,Metaspace,PermGen(在Java 8之前的版本中)和本机堆。

这些内存池中的每一个都可能遇到自己的一组内存问题,例如,异常的内存增长,应用程序速度缓慢或内存泄漏,所有这些最终最终可能以这些内存空间的OutOfMemoryError形式出现。

在本文中,我们将尝试了解这些OutOfMemoryError错误消息的含义,我们应该收集哪些诊断数据来诊断和解决这些问题,并将研究一些工具来收集该数据并进行分析以解决这些内存问题。 本文重点介绍如何在生产环境中处理和防止这些内存问题。

Java HotSpot VM报告的OutOfMemoryError消息明确指示哪个内存空间正在耗尽。 让我们详细查看这些各种OutOfMemoryError消息,了解它们并探究它们可能的原因,以及如何解决和解决它们。

OutOfMemoryError:Java堆空间

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Unknown Source)
at java.lang.String.<init>(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
at com.abc.ABCParser.dump(ABCParser.java:23)
at com.abc.ABCParser.mainABCParser.java:59)

此消息表示JVM在Java堆中没有剩余的可用空间,并且无法继续执行程序。 此类错误的最常见原因是指定的最大Java堆大小不足以容纳完整的活动对象集。 检查Java堆是否足够大以包含JVM中的所有活动对象的一种简单方法是检查GC日志。

688995.775: [Full GC [PSYoungGen: 46400K->0K(471552K)] [ParOldGen: 1002121K->304673K(1036288K)] 1048
521K->304673K(1507840K) [PSPermGen: 253230K->253230K(1048576K)], 0.3402350 secs] [Times: user=1.48 
sys=0.00, real=0.34 secs]

从上面的日志条目中我们可以看到,在Full GC ,堆占用率从1GB(1048521K)下降到305MB(304673K),这意味着分配给堆的1.5GB(1507840K)足够​​大,可以容纳实时数据集。

现在,让我们看一下以下GC活动:

20.343: [Full GC (Ergonomics) [PSYoungGen: 12799K->12799K(14848K)] [ParOldGen: 33905K->33905K(34304K)] 46705K- >46705K(49152K), [Metaspace: 2921K->2921K(1056768K)], 0.4595734 secs] [Times: user=1.17 sys=0.00, real=0.46 secs]
...... <snip> several Full GCs </snip> ......
22.640: [Full GC (Ergonomics) [PSYoungGen: 12799K->12799K(14848K)] [ParOldGen: 33911K->33911K(34304K)] 46711K- >46711K(49152K), [Metaspace: 2921K->2921K(1056768K)], 0.4648764 secs] [Times: user=1.11 sys=0.00, real=0.46 secs]
23.108: [Full GC (Ergonomics) [PSYoungGen: 12799K->12799K(14848K)] [ParOldGen: 33913K->33913K(34304K)] 46713K- >46713K(49152K), [Metaspace: 2921K->2921 K(1056768K)], 0.4380009 secs] [Times: user=1.05 sys=0.00, real=0.44 secs]
23.550: [Full GC (Ergonomics) [PSYoungGen: 12799K->12799K(14848K)] [ParOldGen: 33914K->33914K(34304K)] 46714K- >46714K(49152K), [Metaspace: 2921K->2921 K(1056768K)], 0.4767477 secs] [Times: user=1.15 sys=0.00, real=0.48 secs]
24.029: [Full GC (Ergonomics) [PSYoungGen: 12799K->12799K(14848K)] [ParOldGen: 33915K->33915K(34304K)] 46715K- >46715K(49152K), [Metaspace: 2921K->2921 K(1056768K)], 0.4191135 secs] [Times: user=1.12 sys=0.00, real=0.42 secs] Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at oom.main(oom.java:15)

从转储中“ Full GC”消息的出现频率可以看出,有几个背对背的Full GC试图回收Java堆中的空间,但是堆已完全填满,GC无法释放任何空间。 这些频繁的Full GC会对应用程序性能产生负面影响,从而降低其爬网速度。 本示例建议应用程序的堆要求大于指定的Java堆大小。 增加堆大小将有助于避免这些完整的GC,并避免OutOfMemoryError。 可以使用-Xmx JVM选项增加Java堆大小:

java –Xmx1024m –Xms1024m Test

OutOfMemoryError也可以指示应用程序中的内存泄漏。 内存泄漏通常很难检测,尤其是缓慢的内存泄漏。 众所周知,当应用程序无意间持有对堆中对象的引用时,就会发生内存泄漏,从而阻止了它们被垃圾回收。 这些无意保留的对象会随着时间的推移在堆中增长,最终填满整个Java堆空间,从而导致频繁的垃圾回收,并最终导致程序因OutOfMemoryError终止。

请注意,即使在生产环境中,启用GC日志记录也是一个好主意,以方便在发生内存问题时进行检测和故障排除。 以下选项可用于打开GC日志记录:

-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:<gc log file>

检测内存泄漏的第一步是监视应用程序的实时运行。 实时设置是完整GC后使用的Java堆的数量。 如果即使在应用程序已达到稳定状态并处于稳定负载之后,实时设置仍在随时间增加,则可能表明内存泄漏。 可以使用Java VisualVM,Java Mission Control和JConsole等工具监视堆使用情况,也可以从GC日志中提取它们。

Java堆:诊断数据的收集

在本节中,我们将探讨应该收集哪些诊断数据以对Java堆中的OutOfMemoryErrors进行故障排除,以及可以帮助我们收集所需诊断数据的工具。

堆转储

堆转储是在对内存泄漏进行故障排除时可以收集的最重要的数据。 可以使用jcmd,jmap,JConsole和HeapDumpOnOutOfMemoryError JVM选项收集堆转储,如下所示。

  • jcmd <process id/main class> GC.heap_dump filename=heapdump.dmp
  • jmap -dump:format=b,file=snapshot.jmap pid
  • JConsole utility, using Mbean HotSpotDiagnostic
  • -XX:+HeapDumpOnOutOfMemoryError
java -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx20m -XX:+HeapDumpOnOutOfMemoryError oom 
0.402: [GC (Allocation Failure) [PSYoungGen: 5564K->489K(6144K)] 5564K->3944K(19968K), 0.0196154 secs] [Times: user=0.05 sys=0.00, real=0.02 secs] 
0.435: [GC (Allocation Failure) [PSYoungGen: 6000K->496K(6144K)] 9456K->8729K(19968K), 0.0257773 secs] [Times: user=0.05 sys=0.00, real=0.03 secs] 
0.469: [GC (Allocation Failure) [PSYoungGen: 5760K->512K(6144K)] 13994K->13965K(19968K), 0.0282133 secs] [Times: user=0.05 sys=0.00, real=0.03 secs] 
0.499: [Full GC (Ergonomics) [PSYoungGen: 512K->0K(6144K)] [ParOldGen: 13453K->12173K(13824K)] 13965K- 
>12173K(19968K), [Metaspace: 2922K->2922K(1056768K)], 0.6941054 secs] [Times: user=1.45 sys=0.00, real=0.69 secs] 1.205: [Full GC (Ergonomics) [PSYoungGen: 5632K->2559K(6144K)] [ParOldGen: 12173K->13369K(13824K)] 17805K- 
>15929K(19968K), [Metaspace: 2922K->2922K(1056768K)], 0.3933345 secs] [Times: user=0.69 sys=0.00, real=0.39 secs] 
1.606: [Full GC (Ergonomics) [PSYoungGen: 4773K->4743K(6144K)] [ParOldGen: 13369K->13369K(13824K)] 18143K- 
>18113K(19968K), [Metaspace: 2922K->2922K(1056768K)], 0.3009828 secs] [Times: user=0.72 sys=0.00, real=0.30 secs] 
1.911: [Full GC (Allocation Failure) [PSYoungGen: 4743K->4743K(6144K)] [ParOldGen: 13369K->13357K(13824K)] 18113K- 
>18101K(19968K), [Metaspace: 2922K->2922K(1056768K)], 0.6486744 secs] [Times: user=1.43 sys=0.00, real=0.65 secs] 
java.lang.OutOfMemoryError: Java heap space 
Dumping heap to java_pid26504.hprof ... 
Heap dump file created [30451751 bytes in 0.510 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

 at java.util.Arrays.copyOf(Arrays.java:3210)
 at java.util.Arrays.copyOf(Arrays.java:3181)
 at java.util.ArrayList.grow(ArrayList.java:261)
 at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
 at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
 at java.util.ArrayList.add(ArrayList.java:458)
 at oom.main(oom.java:14)

请注意,并行垃圾收集器可以通过频繁调用背对背的Full GC来连续尝试释放堆上的空间,即使这样做的收益很小并且堆几乎已满。 这会影响应用程序的性能,并可能延迟系统的重新启动。 可以通过调整-XX:GCTimeLimit-XX:GCHeapFreeLimit的值来避免这种情况。

GCTimeLimit设置GC可以花费的总时间的上限(以总时间的百分比表示)。 其默认值为98%。 减小该值将减少可用于垃圾回收的时间量。 GCHeapFreeLimit对垃圾回收后应释放的空间量设置一个下限,以最大堆的百分比表示。 默认值为2%。 增大此值意味着GC应该回收更多的堆空间。 如果前5个连续的GC(可能是次要的GC或完整的GC)无法将GC的成本保持在GCTimeLimit以下并且无法释放GCHeapFreeLimit空间,则在Full GC之后引发OutOfMemoryError

例如,将GCHeapFreeLimit设置为8%可以帮助垃圾收集器在无法回收至少8%的堆并且连续5个GC超过GCTimeLimit时,不会陷入调用背对背Full GC的循环中。

堆直方图

有时,我们需要快速了解堆中正在增长的内容,而绕过使用内存分析工具收集和分析堆转储的漫长路线。 堆直方图可以使我们快速查看堆中存在的对象,比较这些直方图可以帮助我们找到Java堆中最主要的种植者。

  • -XX:+PrintClassHistogram and Control+Break
  • jcmd <process id/main class> GC.class_histogram filename=Myheaphistogram
  • jmap -histo pid
  • jmap -histo <java> core_file

下面的示例输出显示String,Double,Integer和Object[]实例在Java堆中占据了最大的空间,并且随着时间的推移数量不断增长,表明这些可能潜在地导致内存泄漏:

Java飞行记录

启用堆统计信息的Flight Records可以通过向我们显示堆对象和堆中时间最长的增长者,对解决内存泄漏很有帮助。 要启用堆统计信息,可以使用Java Mission Control并通过转到“窗口->飞行记录模板管理器”来启用“堆统计信息”,如下所示。

或通过将enabled-statistics-enabled设置为true在.jfc文件中手动编辑。

<event path="vm/gc/detailed/object_count">
    <setting name="enabled" control="heap-statistics-enabled">true</setting>
    <setting name="period">everyChunk</setting>
</event>

然后可以使用以下任何一种方式来创建飞行记录:

  • JVM Flight Recorder选项,例如

-XX:+UnlockCommercialFeatures -XX:+FlightRecorder
-XX:StartFlightRecording=delay=20s,duration=60s,name=MyRecording,

filename=C:\TEMP\myrecording.jfr,settings=profile

  • Java诊断命令:jcmd

jcmd 7060 JFR.start name=MyRecording settings=profile delay=20s duration=2m filename=c:\TEMP\myrecording.jfr

  • Java任务控制

飞行记录可以帮助我们确定泄漏对象的类型,但是要找出导致这些对象泄漏的原因,我们需要堆转储。

Java Heap:诊断数据分析

堆转储分析

可以使用以下工具分析堆转储:

  • Eclipse MAT- (内存分析器工具)-由社区开发的用于分析堆转储的工具。 它提供的一些令人惊奇的功能包括:
    • 泄漏嫌疑人:它可以检查堆转储中是否有泄漏可疑对象,以报告可疑保留大量堆的对象
    • 直方图:列出每个类的对象数,以及这些对象所保留的浅层和保留堆。 直方图中的对象可以使用正则表达式轻松排序或过滤。 这有助于放大并集中于我们怀疑可能泄漏的对象。 它还具有比较来自两个堆转储的直方图的功能,并可以显示每个类的实例数量的差异。 这有助于找到Java堆中的顶级增长者,可以对其进行进一步检查以确定确定堆中那些对象的根。
    • 无法访问的对象:MAT的一项惊人功能是它允许在其工作对象集中包含或排除无法访问/死的对象。 如果我们不想在下一个GC周期中查看那些无法访问且符合收集条件的对象,而仅对可访问对象感兴趣,那么此功能非常方便。
    • 重复类:显示由多个类加载器加载的重复类。
    • GC根目录的路径:可以显示指向GC根目录(JVM自身保持活动的对象)的引用链,这些参考链负责将对象保留在堆中
    • OQL:我们可以使用对象查询语言来浏览堆转储中的对象。 其丰富的OQL有助于编写复杂的查询,以帮助深入研究堆转储。
  • Java VisualVM-用于监视,分析和排除Java应用程序故障的多合一工具。 它可以作为JDK工具使用,也可以从GitHub下载。 它提供的功能之一是堆转储分析。 它具有创建要监视的应用程序的堆转储的功能,还可以加载和解析它们。 从堆转储中,它可以显示类直方图,类的实例,还可以帮助查找特定实例的GC根。
  • jhat命令行工具(在我们的<jdk> / bin文件夹中。)通过使用任何Web浏览器浏览堆转储中的对象来提供堆转储分析。 默认情况下,Web服务器从端口7000启动。jhat支持各种预先设计的查询和对象查询语言(OQL),以浏览堆转储中的对象。
  • 用于Java Mission Control的JOverflow插件-实验性插件,使Java Mission Control可以执行简单的堆转储分析并报告可能浪费内存的位置。
  • 带有堆转储分析器的Yourkit Commercial Java Profiler,具有其他工具提供的几乎所有功能。 此外,YourKit还提供:
    • 可达范围:它不仅可以列出可达对象和不可达对象,还可以根据其可达范围(即强可达,弱/软可达或不可达)分配对象。
    • 内存检查:YourKit提供了一组全面的内置查询,而不是临时查询功能,这些查询可以检查内存以查找反模式,并提供常见内存问题的原因和解决方案。

我经常使用Eclipse MAT,并且发现它对分析堆转储非常有帮助。

MAT具有高级功能,包括直方图以及将它们与其他直方图进行比较的功能。 这清楚地显示了内存中正在增长的内容,并保留了Java堆中的最大空间。 我非常喜欢的功能之一是“合并GC根的最短路径”,它有助于找到负责保留意外持有的对象的对象线索。 例如,在下面的参考链中,ThreadLocalDateFormat对象由ThreadLocalMap $ Entry对象的“ value”字段保存在堆中。 从ThreadLocalMap中删除ThreadLocalMap $ Entry之前,不会收集ThreadLocalDateFormat。

weblogic.work.ExecuteThread @ 0x6996963a8 [ACTIVE] ExecuteThread: '203' for queue: 'weblogic.kernel.Default (self-tuning)' Busy Monitor, Thread| 1 | 176 | 40 | 10,536

'- threadLocals java.lang.ThreadLocal$ThreadLocalMap @ 0x69c2b5fe0 | 1 | 24 | 40 | 7,560

'- table java.lang.ThreadLocal$ThreadLocalMap$Entry[256] @ 0x6a0de2e40 | 1 | 1,040 | 40 | 7,536

'- [116] java.lang.ThreadLocal$ThreadLocalMap$Entry @ 0x69c2ba050 | 1 | 32 | 40 | 1,088

'- value weblogic.utils.string.ThreadLocalDateFormat @ 0x69c23c418 | 1 | 40 | 40 | 1,056

通过这种方法,我们可以在堆中找到顶级种植者的根源,并了解内存中泄漏的内容。

Java任务控制

Java Mission Control在JDK的<jdk> / bin文件夹中可用。 启用了“堆统计信息”功能后收集的飞行记录可以极大地帮助您解决内存泄漏问题。 我们可以在“内存”->“对象统计”下查看“对象统计”。 此视图显示对象直方图,其中包括每种对象类型占用的堆的百分比。 它也向我们显示了堆中的顶级种植者,大多数时候它们与泄漏的对象有直接的关联。

由于完成而导致OutOfMemoryError

OutOfMemoryError也可能由于过度使用终结器引起。 具有终结器的对象(即finalize()方法)可能会延迟回收它们占用的空间。 终结器线程需要调用实例的finalize()方法,然后才能回收这些实例并释放其堆空间。 如果终结器线程不符合对象有资格终结的速率(添加到终结器的队列中,以便它可以调用其finalize()方法),则即使对象堆积,JVM也会失败并显示OutOfMemoryError在完成者队列中的成员有资格被收集。 因此,非常重要的一点是,要确保我们不会因为有大量待定对象而耗尽内存。

我们可以使用以下工具来监视即将完成的对象的数量:

  • J控制台

我们可以将JConsole连接到正在运行的进程,并在VM Summary页面中监视待完成的对象数量,如下图所示。

  • jmap – finalizerinfo
D:\tests\GC_WeakReferences>jmap -finalizerinfo 29456 
Attaching to process ID 29456, please wait...
Debugger attached successfully. Server compiler detected.
JVM version is 25.122-b08
Number of objects pending for finalization: 10
  • 堆转储

几乎所有的堆转储分析工具都会显示有关即将完成的对象的详细信息。

Java VisualVM的输出

Date taken: Fri Jan 06 14:48:54 PST 2017
	File: D:\tests\java_pid19908.hprof
	File size: 11.3 MB
 
	Total bytes: 10,359,516
	Total classes: 466
	Total instances: 105,182
	Classloaders: 2
	GC roots: 419
	Number of objects pending for finalization: 2

OutOfMemoryError:PermGen空间

java.lang.OutOfMemoryError: PermGen space

众所周知,PermGen已从Java 8开始删除,因此,如果您在Java 8或更高版本上运行,请随时跳过此部分。

直到Java 7为止,PermGen(“永久生成”的缩写)一直用于存储类定义及其元数据。 PermGen的意外增长或此内存空间中的OutOfMemoryError意味着无法按预期方式卸载类,或者指定的PermGen大小太小而无法容纳所有已加载的类及其元数据。

为了确保PermGen能够根据应用程序要求调整大小,我们应该监视其使用情况并使用以下JVM选项进行相应的配置:

–XX:PermSize=n –XX:MaxPermSize=m

OutOfMemoryError:元空间

MetaSpace的OutOfMemoryError示例:

java.lang.OutOfMemoryError: Metaspace

从Java 8开始,类元数据存储在元空间中。 元空间不是Java堆的一部分,而是在本机内存之外分配的。 因此它是无限的,并且仅受计算机上可用的本机内存量的限制。 但是,可以通过使用MaxMetaspaceSize选项来限制元空间的大小。

如果并且当Metaspace的使用量达到MaxMetaspaceSize指定的最大限制时,我们可能会遇到OutOfMemoryError。 与其他空间一样,这可能是由于元空间的大小不足或类加载器/类泄漏引起的。 在后面的部分中,我们将探索可用于对元空间中的内存泄漏进行故障排除的诊断工具。

OutOfMemoryError:压缩的类空间

java.lang.OutOfMemoryError: Compressed class space

如果启用UseCompressedClassesPointers (如果UseCompressedOops处于打开状态,则默认情况下是默认设置),则将本机内存的两个单独区域用于类及其元数据。 使用UseCompressedClassesPointers ,用32位值表示64位类指针,并且这些压缩的类指针存储在压缩的类空间中。 默认情况下,此压缩类空间的大小为1GB,可以使用CompressedClassSpaceSize.进行配置CompressedClassSpaceSize.
MaxMetaspaceSize设置这两个区域的总提交大小的上限-压缩类空间的提交空间和类元数据。

启用UseCompressedClassesPointers的GC日志的示例输出。 为元空间报告的承诺和保留空间包括压缩类空间的承诺和保留空间。

Metaspace     used 2921K, capacity 4486K, committed 4864K, reserved 1056768K
  class space used 288K, capacity 386K, committed 512K, reserved 1048576K

PermGen和元空间:数据收集和分析工具

可以使用Java Mission Control,Java VisualVM和JConsole监视PermGen和元空间的占用。 GC日志可以帮助我们了解Full GC之前和之后的PermGen / Metaspace使用情况,并查看是否由于PermGen / Metaspace已满而调用任何Full GC。

确保按预期卸载类也很重要。 可以使用以下方法跟踪类的加载和卸载:

-XX:+TraceClassUnloading –XX:+TraceClassLoading

重要的是要意识到一种综合症,即应用程序无意中将某些JVM选项从开发者升级为产品,从而带来不利后果。 一个这样的选项是-Xnoclassgc,它指示JVM在垃圾回收期间不要卸载类。 现在,如果应用程序需要加载大量类或在运行时无法访问某些类集,并且加载另一组新类,并且该应用程序恰巧以–Xnoclassgc运行,则存在碰到PermGen / Metaspace的最大容量,因此出现OutOfMemoryError失败。 因此,如果不确定为什么指定此选项,则最好将其删除,并让垃圾收集器在有资格进行收集时卸载类。

可以使用本机内存跟踪器(NMT)来跟踪已加载类的数量及其所使用的内存。 我们将在下面的“ OutOfMemoryError:本机内存”部分中详细讨论此工具。

请注意,使用并发MarkSweep Collector(CMS),应启用以下选项,以确保在CMS并发收集周期中卸载类: –XX:+CMSClassUnloadingEnabled

在Java 7中,此标志的默认值为false,而在Java 8中则默认启用。

映射

“ jmap –permstat”显示类加载器统计信息,例如类加载器,这些类加载器加载的类数以及这些类加载器是否已失效。 它还告诉我们PermGen中存在的内部字符串的总数,以及所加载的类及其元数据所占用的字节数。 所有这些信息对于确定可能填充PermGen的内容非常有用。 这是一个示例打印输出,显示所有这些统计信息。 您可以在清单的最后一行看到摘要。

$ jmap -permstat 29620
Attaching to process ID 29620, please wait...
Debugger attached successfully. Client compiler detected.
JVM version is 24.85-b06
12674 intern Strings occupying 1082616 bytes. finding class loader instances ..
 done. computing per loader stat ..done. please wait.. computing liveness.........................................done.
class_loader	classes bytes parent_loader   alive?  type
<bootstrap> 1846 5321080  null  live   <internal>
0xd0bf3828  0   0  	null   live    sun/misc/Launcher$ExtClassLoader@0xd8c98c78
0xd0d2f370  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0c99280  1   1440  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0b71d90  0   0   0xd0b5b9c0	live 	  java/util/ResourceBundle$RBClassLoader@0xd8d042e8
0xd0d2f4c0  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0b5bf98  1   920   0xd0b5bf38 dead   sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0c99248  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0d2f488  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0b5bf38  6   11832  0xd0b5b9c0 dead  sun/reflect/misc/MethodUtil@0xd8e8e560
0xd0d2f338  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0d2f418  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0d2f3a8  1   904 	null   dead	   sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0b5b9c0  317 1397448 0xd0bf3828 live sun/misc/Launcher$AppClassLoader@0xd8cb83d8
0xd0d2f300  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0d2f3e0  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0ec3968  1   1440  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0e0a248  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0c99210  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0d2f450  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0d2f4f8  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
0xd0e0a280  1   904  	null   dead    sun/reflect/DelegatingClassLoader@0xd8c22f50
 
total = 22  	2186    6746816   N/A   alive=4, dead=18   	N/A

从Java 8开始,jmap –clstats <pid>打印出有关类加载器及其活动性的类似信息,但这将显示加载到Metaspace中而不是PermGen的类的数量和大小。

jmap -clstats 26240
Attaching to process ID 26240, please wait...
Debugger attached successfully. Server compiler detected. JVM version is 25.66-b00 finding class loader instances ..done. computing per loader stat ..done. please wait.. computing liveness.liveness analysis may be inaccurate ...
class_loader	 classes bytes parent_loader alive? type
<bootstrap>        513 950353 null live <internal>
0x0000000084e066d0 8 24416  0x0000000084e06740 live sun/misc/Launcher$AppClassLoader@0x0000000016bef6a0
0x0000000084e06740 0 0      null live sun/misc/Launcher$ExtClassLoader@0x0000000016befa48
0x0000000084ea18f0 0 0 0x0000000084e066d0 dead java/util/ResourceBundle$RBClassLoader@0x0000000016c33930
 
total = 4   	521 	974769      N/A     	alive=3, dead=1 	N/A

堆转储

如上一节所述,Eclipse MAT,jat,Java VisualVM,JOverflow JMC插件和Yourkit是一些工具,可以帮助分析堆转储以分析OutOfMemoryErrors。 但是堆转储对于解决PermGen和元空间内存问题也很有用。 Eclipse MAT提供了一个非常好的功能,称为“重复类”,它显示了由不同的类加载器实例多次加载的所有类。 由不同的类加载器加载的一定数量的重复类可能是应用程序设计的一部分。 但是,如果它们随着时间的推移而持续增长,那是一个危险信号,应该对其进行调查。 这在运行于相同基础JVM实例上的应用程序服务器托管的应用程序中最常见,并且被取消部署并重新部署了几次。 如果取消部署应用程序未释放对它创建的类加载器的所有引用,则JVM将无法卸载那些类加载器加载的类,并且新部署的应用程序将加载这些类的新集合。一个新的类加载器实例。

此快照显示JaxbClassLoader加载了类的重复副本,这之所以发生,是因为该应用程序不恰当地为每个XML到Java类绑定创建了JAXBContext的新实例。

jcmd

jcmd <pid / classname> GC.class_stats提供了有关已加载类大小的更多详细信息,使我们能够看到Metaspace中每个类所占用的空间,如以下示例输出所示。

jcmd 2752 GC.class_stats 2752:
Index  Super  InstBytes  KlassBytes  annotations  CpAll  MethodCount  Bytecodes  MethodAll  ROAll   RWAll   Total  ClassName
1  	357 	821632 	536       	0      	352 	2       	13     	616    	184 	1448	1632 java.lang.ref.WeakReference
2  	-1  	295272 	480       	0      	0   	0       	0      	0      	24  	584 	608 [Ljava.lang.Object;
3  	-1  	214552 	480       	0      	0   	0       	0      	0      	24  	584 	608 [C
4  	-1  	120400 	480       	0      	0   	0       	0      	0      	24  	584 	608 [B
5  	35  	78912  	624       	0      	8712	94      	4623   	26032  	12136   24312   36448 java.lang.String
6  	35  	67112  	648       	0      	19384   130     	4973   	25536  	16552   30792   47344 java.lang.Class
7  	9   	24680  	560       	0      	384 	1       	10     	496    	232 	1432	1664 java.util.LinkedHashMap$Entry
8  	-1  	13216  	480       	0      	0   	0       	0      	0      	48  	584 	632 [Ljava.lang.String;
9  	35  	12032  	560       	0      	1296	7       	149    	1520   	880 	2808	3688 java.util.HashMap$Node
10 	-1  	8416   	480       	0      	0   	0       	0      	0      	32  	584 	616 [Ljava.util.HashMap$Node;
11 	-1  	6512   	480       	0      	0   	0       	0      	0      	24  	584 	608 [I
12 	358 	5688   	720       	0      	5816	44      	1696   	8808   	5920	10136   16056 java.lang.reflect.Field
13 	319 	4096   	568       	0      	4464	55      	3260   	11496  	7696	9664	17360 java.lang.Integer
14 	357 	3840   	536       	0      	584 	3       	56     	496    	344 	1448	1792 java.lang.ref.SoftReference
15 	35  	3840   	584       	0      	1424	8       	240    	1432   	1048	2712	3760 java.util.Hashtable$Entry
16 	35  	2632   	736       	368    	8512	74      	2015   	13512  	8784	15592   24376 java.lang.Thread
17 	35  	2496   	504       	0      	9016	42      	2766   	9392   	6984	12736   19720 java.net.URL
18 	35  	2368   	568       	0      	1344	8       	223    	1408   	1024	2616	3640 java.util.concurrent.ConcurrentHashMap$Node
…<snip>…
577	35  	0      	544       	0      	1736	3       	136    	616    	640 	2504	3144 sun.util.locale.provider.SPILocaleProviderAdapter$1
578	35  	0      	496       	0      	2736	8       	482    	1688   	1328	3848	5176 sun.util.locale.provider.TimeZoneNameUtility
579	35  	0      	528       	0      	776 	3       	35     	472    	424 	1608	2032 sun.util.resources.LocaleData$1
580	442 	0      	608       	0      	1704	10      	290    	1808   	1176	3176	4352 sun.util.resources.OpenListResourceBundle
581	580 	0      	608       	0      	760 	5       	70     	792    	464 	1848	2312 sun.util.resources.TimeZoneNamesBundle
          	1724488 	357208    	1536   	1117792 7754    	311937 	1527952	1014880 2181776 3196656 Total
            	53.9%  	11.2%    	0.0%   	35.0%	-      	9.8%   	47.8%  	31.7%   68.3%   100.0%
Index  Super  InstBytes  KlassBytes  annotations  CpAll  MethodCount  Bytecodes  MethodAll  ROAll   RWAll   Total  ClassName

从此输出中,我们可以看到已加载类的名称( ClassName ),每个类占用的字节数(K lassBytes ),每个类的实例占用的字节数(InstBytes),每个类中的方法数( MethodCount ),已占用的空间由字节码( ByteCodes)等决定。

请注意,在Java 8中,此诊断命令要求使用‑XX:+ UnlockDiagnosticVMOptions选项启动Java进程。

jcmd 33984 GC.class_stats 33984:
GC.class_stats command requires -XX:+UnlockDiagnosticVMOptions

在Java 9中,此诊断命令不需要-XX:+ UnlockDiagnosticVMOption。

OutOfMemoryError:本机内存

本机内存的OutOfMemoryError的一些示例是:
由于交换空间不足,导致OutOfMemoryError:

# A fatal error has been detected by the Java Runtime Environment:
 
#
# java.lang.OutOfMemoryError: requested 32756 bytes for ChunkPool::allocate. Out of swap space?
#
#  Internal Error (allocation.cpp:166), pid=2290, tid=27 #  Error: ChunkPool::allocate

由于进程内存不足,导致OutOfMemoryError

# A fatal error has been detected by the Java Runtime Environment:
#
# java.lang.OutOfMemoryError : unable to create new native Thread
这些错误清楚地告诉我们,JVM无法从本机内存中进行分配,这可能是由于以下事实:进程本身正在消耗所有本机内存,或者系统上的其他进程正在消耗该内存。本机内存。 在使用“ pmap”(或其他本机内存映射工具)监视本机堆的使用情况并适当配置了Java堆,线程数和堆栈大小之后,请注意确保为本机堆留出足够的空间,如果我们发现本机堆使用量随时间增长,并且最终遇到OutOfMemoryError,则可能表示本机内存泄漏。

带有64位JVM的本机堆OutOfMemoryError

使用32位JVM运行时,进程大小的最大限制为4GB,因此使用32位Java进程的本机内存很有可能用尽。 但是,当使用64位JVM运行时,我们可以访问无限的内存,并且从技术上讲,我们希望永远不会用完本机堆。 但实际上并非如此,观察到在64位JVM中也发生本机堆OutOfMemoryErrors并不少见。 这是由于以下事实:默认情况下,64位JVM启用了一个称为CompressedOops的功能,并且该功能的实现确定了Java堆应放在地址空间中的位置。 Java堆的位置可以限制本机内存的最大容量。 下面的内存映射显示Java堆分配在8GB的地址边界上,为本机堆保留大约4GB的空间。 如果此应用程序密集分配了本机内存,并且需要超过4GB,则即使系统上有大量可用内存,它也会引发本机堆OutOfMemoryError。

0000000100000000 8K r-x-- /sw/.es-base/sparc/pkg/jdk-1.7.0_60/bin/sparcv9/java
0000000100100000 8K rwx-- /sw/.es-base/sparc/pkg/jdk-1.7.0_60/bin/sparcv9/java
0000000100102000 56K rwx--	    [ heap ]
0000000100110000 2624K rwx--	[ heap ]   <--- native Heap
00000001FB000000 24576K rw---	[ anon ]   <--- Java Heap starts here
0000000200000000 1396736K rw---	[ anon ]
0000000600000000 700416K rw---	[ anon ]

可以通过使用选项-XX:HeapBaseMinAddress = n来指定Java堆应从其开始的地址来解决此问题。 将其设置为更高的地址将为本机堆留出更多空间。

请在此处查看有关如何诊断,排除故障和解决此问题的更多详细信息

本机堆:诊断工具

让我们看一下内存泄漏检测工具,这些工具可以帮助我们了解本机内存泄漏的原因。

本机内存跟踪

JVM具有一项称为本机内存跟踪(NMT)的强大功能,可用于跟踪JVM内部使用的本机内存。 请注意,它无法跟踪JVM外部或本机库分配的内存。 通过使用以下简单的两个步骤,我们可以监视JVM的本机内存使用情况:

  • 在启用NMT的情况下启动该过程。 可以将输出级别设置为“摘要”或“详细”级别:
    • -XX:NativeMemoryTracking =摘要
    • -XX:NativeMemoryTracking =详细信息
  • 使用jcmd获取本机内存使用情况详细信息:
    • jcmd <pid> VM.native_memory

NMT输出示例:

d:\tests>jcmd 90172 VM.native_memory  90172:
Native Memory Tracking:
Total: reserved=3431296KB, committed=2132244KB
-                 Java Heap (reserved=2017280KB, committed=2017280KB)
            (mmap: reserved=2017280KB, committed=2017280KB)
-                 Class (reserved=1062088KB, committed=10184KB)
            (classes #411)
            (malloc=5320KB #190)
            (mmap: reserved=1056768KB, committed=4864KB)
-                  Thread (reserved=15423KB, committed=15423KB)
            (thread #16)
            (stack: reserved=15360KB, committed=15360KB)
            (malloc=45KB #81)
            (arena=18KB #30)
-                 Code (reserved=249658KB, committed=2594KB)
            (malloc=58KB #348)
            (mmap: reserved=249600KB, committed=2536KB)
-                 GC (reserved=79628KB, committed=79544KB)
            (malloc=5772KB #118)
            (mmap: reserved=73856KB, committed=73772KB)
-                 Compiler (reserved=138KB, committed=138KB)
            (malloc=8KB #41)
            (arena=131KB #3)
-                 Internal (reserved=5380KB, committed=5380KB)
            (malloc=5316KB #1357)
            (mmap: reserved=64KB, committed=64KB)
-                 Symbol (reserved=1367KB, committed=1367KB)
            (malloc=911KB #112)
            (arena=456KB #1)
-                 Native Memory Tracking (reserved=118KB, committed=118KB)
            (malloc=66KB #1040)
            (tracking overhead=52KB)
-                 Arena Chunk (reserved=217KB, committed=217KB)
            (malloc=217KB)

有关访问NMT数据的所有jcmd命令以及如何读取其输出的更多详细信息,请参见此处

本机内存泄漏检测工具

对于源自JVM外部的本机内存泄漏,我们需要依靠本机内存泄漏工具进行检测和故障排除。 诸如dbxlibumemvalgrindpurify等本机工具可以拯救我们,以应对外部JVM本机内存泄漏。

摘要

对内存问题进行故障排除可能非常艰巨和棘手,但是正确的方法和正确的工具集无疑可以轻松轻松地解决它们。 正如我们所看到的,Java HotSpot JVM报告了不同类型的OutOfMemoryError消息,因此,清楚地理解这些错误消息非常重要,并且在我们的工具箱中具有广泛的诊断和故障排除工具可以诊断和根除这些错误消息。问题。

翻译自: https://www.infoq.com/articles/Troubleshooting-Java-Memory-Issues/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

java应用程序占用高内存

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值