本文将为您提供一个教程,使您可以确定活动应用程序Java线程保留Java堆空间的数量和位置。 将提供来自Oracle Weblogic 10.0生产环境的真实案例研究,以使您更好地理解分析过程。
我们还将尝试证明过多的垃圾回收或Java堆空间的内存占用问题通常不是由真正的内存泄漏引起的,而是由线程执行模式和大量的短期对象引起的。
背景
您可能从我过去的JVM概述文章中看到,Java线程是JVM基础的一部分。 您的Java堆空间内存占用量不仅受静态对象和寿命长的对象的驱动,还受寿命短的对象的驱动。
通常会错误地认为OutOfMemoryError问题是由于内存泄漏引起的。 我们经常忽略错误的线程执行模式和它们在Java堆上“保留”到执行完成的短暂对象。 在这种有问题的情况下:
- 您的“预期”应用程序的短期/无状态对象(XML,JSON数据有效载荷等)被线程保留的时间过长(线程锁争用,巨大的数据有效载荷,来自远程系统的响应时间慢等)。
- 最终,这样的短期对象被垃圾收集器提升为长期对象空间,例如OldGen / tenured空间
- 副作用是,这导致OldGen空间快速填充,从而增加了Full GC(主要集合)的频率
- 根据情况的严重性,这可能导致过度的GC垃圾收集,JVM暂停时间增加,并最终导致OutOfMemoryError:Java堆空间
- 您的应用程序现在关闭,您现在对正在发生的事情感到困惑
- 最后,您正在考虑增加Java堆或寻找内存泄漏……您是否真的走对了?
在上述情况下,您需要查看线程执行模式,并确定它们在给定时间保留多少内存。
好了,我得到了图片,但是线程堆栈的大小呢?
避免线程堆栈大小和Java内存保留之间的混淆是非常重要的。 线程堆栈大小是JVM用于存储每个方法调用的特殊内存空间。 当线程调用方法A时,它将调用“推”到堆栈上。 如果方法A调用方法B,它也会被压入堆栈。 一旦方法执行完成,调用便从堆栈中“弹出”。
由于此类线程方法调用而创建的Java对象在Java堆空间上分配。 绝对增加线程堆栈大小不会有任何效果。 处理java.lang.stackoverflowerror或OutOfMemoryError:无法创建新的本机线程问题时,通常需要调整线程堆栈的大小。
案例研究和问题背景
以下分析基于我们最近调查的真实生产问题。
- 在用户Web界面进行了一些更改(使用Google Web Toolkit和JSON作为数据有效负载)之后,从Weblogic 10.0生产环境中观察到严重的性能下降。
- 初步分析确实揭示了OutOfMemoryError的几种情况:Java堆空间错误以及过多的垃圾回收。 在发生OOM事件后,会自动生成Java堆转储文件(-XX:+ HeapDumpOnOutOfMemoryError)
- 对详细:gc日志的分析确实确认了32位HotSpot JVM OldGen空间(1 GB容量)的完全耗尽
- 在问题发生之前和过程中也生成了线程转储快照
- 当时唯一可以解决的问题是观察到问题时重新启动受影响的Weblogic服务器
- 最终对变更进行了回滚,这确实解决了这种情况
团队首先从引入的新代码中怀疑了内存泄漏问题。
线程转储分析:寻找可疑对象…
我们所做的第一步是对生成的线程转储数据进行分析。 线程转储通常会向您显示在Java堆上分配内存的罪魁祸首线程。 它还将揭示试图从远程系统发送和接收数据有效载荷的任何占用线程或阻塞线程。
我们注意到的第一个模式是从Weblogic托管服务器(JVM进程)观察到的OOM事件和STUCK线程之间具有良好的相关性。 在找到的主线程模式下面找到:
<10-Dec-2012 1:27:59 o'clock PM EST> <Error> <BEA-000337><[STUCK] ExecuteThread: '22' for queue:'weblogic.kernel.Default (self-tuning)'has been busy for '672' seconds working on the requestwhich is more than the configured time of '600' seconds.
如您所见,以上线程似乎是STUCK或花费很长时间读取和接收来自远程服务器的JSON响应。 一旦找到该模式,下一步就是将该发现与JVM堆转储分析相关联,并确定这些卡住的线程从Java堆中占用了多少内存。
堆转储分析:保留的对象暴露在外!
Java堆转储分析是使用MAT执行的。 现在,我们将列出不同的分析步骤,这些步骤确实使我们可以查明保留的内存大小和源。
1.加载HotSpot JVM堆转储
2.选择HISTOGRAM视图并按“ ExecuteThread”进行过滤
* ExecuteThread是Weblogic内核用于线程创建和执行的Java类*
如您所见,这种观点非常明显。 我们可以看到总共创建了210个Weblogic线程。 这些线程保留的内存总量为806 MB。 这对于具有1 GB OldGen空间的32位JVM进程而言非常重要。 仅此观点就告诉我们问题的核心和内存保留源于线程本身。
3.深入研究线程内存占用量分析
下一步是深入研究线程内存保留。 为此,只需右键单击ExecuteThread类,然后选择:列表对象>带有传出引用。
如您所见,我们能够将线程转储分析中的STUCK线程与堆转储分析中的高内存保留量相关联。 这个发现非常令人惊讶。
4.线程Java局部变量识别
最后的分析步骤确实需要我们扩展一些线程示例并了解内存保留的主要来源。
如您所见,这最后一个分析步骤确实从根本原因上揭示了巨大的JSON响应数据有效载荷。 该模式还通过线程转储分析在早期公开,我们发现一些线程需要很长时间才能读取和接收JSON响应。 数据有效负载占用量巨大的明显症状。
至关重要的是要注意,通过局部方法变量创建的短期对象将显示在堆转储分析中。 但是,其中一些将仅在其父线程中可见,因为这种情况下其他对象未引用它们。 您还需要分析线程堆栈跟踪以识别真正的调用者,然后进行代码检查以确认根本原因。
根据这一发现,在某些情况下,我们的交付团队能够确定最近的JSON错误代码更改正在生成高达45 MB +的巨大JSON数据有效负载。 考虑到该环境使用的是只有1 GB OldGen空间的32位JVM,您可以理解,只有几个线程足以触发严重的性能下降。
该案例研究清楚地表明了适当的容量规划和Java堆分析的重要性,包括从活动应用程序和Java EE容器线程中保留的内存。
其他一切都只是信息
我希望本文能帮助您了解如何通过结合线程转储和堆转储分析来确定活动线程保留的Java堆内存占用量。 现在,如果您不尝试的话,本文将仅停留在文字上,因此,我强烈建议您花一些时间自己学习针对您的应用程序进行的分析过程。
参考: Java Thread:保留了我们的JCG合作伙伴 Pierre-Hugues Charbonneau在Java EE支持模式和Java教程博客上进行的内存分析 。
翻译自: https://www.javacodegeeks.com/2012/12/java-thread-retained-memory-analysis.html