本文将讨论本地内存跟踪(NMT),我们可以使用它来隔离在VM级别出现的任何异常内存增长。
本地内存
本地内存是指应用程序或计算机系统上运行的程序直接可访问的内存空间。这是程序在执行期间存储和操作数据的内存区域。本地内存与托管内存不同,托管内存是由运行时环境或虚拟机管理的内存。
本地内存的主要组件是什么?
本地内存的主要组件可以根据操作系统和使用的编程语言而异。一般来说,本地内存通常包括以下组件。
1. 堆栈: 堆栈是用于在程序执行期间存储局部变量、函数调用和其他相关数据的内存区域。每个线程通常都有自己的堆栈。
2. 堆: 堆是用于动态内存分配的内存区域。这是程序执行期间分配和释放对象和数据结构的地方。在像C和C++这样的语言中,堆由程序员管理,而在像Java这样的语言中,它由运行时环境通过垃圾回收器进行管理。
3. 代码内存: 代码内存,也称为可执行内存或文本段,是存储程序编译代码的内存区域。它包含组成程序可执行代码的指令。
4. 静态和全局变量: 静态和全局变量的内存空间在程序启动时分配,并在整个执行期间保持不变。这些变量可以跨不同的作用域访问,并且可以在多个函数或模块之间共享。
5. 库和DLL: 本地内存还包括由共享库或动态链接库(DLL)占用的内存。这些库包含可由多个程序使用的预编译代码和资源。
6. 操作系统数据结构: 操作系统使用内存来存储其数据结构,例如进程控制块、文件表、网络缓冲区和其他系统相关数据。
7. 内存映射文件: 内存映射文件允许文件像计算机的内存部分一样被访问。文件的一部分可以加载到内存中并直接访问,这对于高效的I/O操作非常有用。
如果我们考虑JVM,我们可以重新定义这些内容如下
方法区
JVM方法区存储类结构,例如元数据、常量运行时池和方法代码。
堆
所有对象、它们相关的实例变量和数组都存储在堆中。这个内存是通用的,可以在多个线程之间共享。
JVM语言栈
Java语言栈存储局部变量及其部分结果。每个线程都有自己的JVM堆栈,在线程创建时同时创建。每当调用方法时,都会创建一个新的帧,并在方法调用过程完成时删除它。
PC寄存器
PC寄存器存储当前执行的Java虚拟机指令的地址。在Java中,每个线程都有自己的PC寄存器。
本地方法堆栈
本地方法堆栈保存与本地库相关的本地代码指令。它是用另一种语言而不是Java编写的。
其他内容
什么是本地内存跟踪(NMT)?
本地内存跟踪(NMT)是由Java虚拟机(JVM)提供的一项功能,允许开发人员监视和分析Java应用程序中本地内存的分配和使用。它有助于识别和诊断本地内存泄漏和过度内存消耗。
回到主题。
我有一个基于Java的应用程序Wso2 MI 4.2.0,我在其中部署了一些服务。我发现与此MI进程相关的高内存使用率,并且随着时间的推移而增加。
像往常一样,我捕获了一个堆转储并尝试找到原因。但令人惊讶的是,即使捕获的堆转储包含少于1GB(配置的最大堆大小为1GB)的数据,当捕获堆转储时,内存图显示超过7GB。
根据目前的情况,我们需要使用NMT来找到线索。
启用NMT并跟踪内存的步骤
1. 启用NMT
需要向JVM传递以下与你的Java应用程序相关的参数
-XX:NativeMemoryTracking=detail
有几个选项。
A. 使用命令行
如果你正在从命令行运行应用程序,请在执行Java命令时添加**-XX:NativeMemoryTracking=detail**选项。
java -XX:NativeMemoryTracking=detail -jar YourApp.jar
B. 使用类似Maven或Gradle的构建工具
如果你正在使用类似Maven或Gradle的构建工具,则可以在构建配置文件中配置JVM选项。例如,在Maven pom.xml文件中,你可以添加以下配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-XX:NativeMemoryTracking=detail</argLine>
</configuration>
</plugin>
</plugins>
</build>
C. 在基于Wso2的产品中
在MI中,我们可以使用启动脚本来设置这些参数
wso2mi-4.2.0/bin/micro-integrator.sh
while [ "$status" = "$START_EXIT_STATUS" ]
do
$JAVACMD \
-Xbootclasspath/a:"$CARBON_XBOOTCLASSPATH" \
$JVM_MEM_OPTS \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath="$CARBON_HOME/repository/logs/heap-dump.hprof" \
-XX:NativeMemoryTracking=detail \
$JAVA_OPTS \
-Dcom.sun.management.jmxremote \
设置参数后,我们需要重新启动服务器。
2. 为NMT定义基线
我们可以使用以下命令
jcmd <PID> VM.native_memory baseline scale=MB
这背后的原因是定义一个参考点,我们可以将内存增量与时间进行比较。
3. 捕获内存详细信息
jcmd <PID> VM.native_memory detail scale=MB > native_memory_detail
通过此命令,我们可以捕获类似下面的本地内存状态快照。
保留 — 系统分配给该进程的保留内存 已提交 — 当前使用量
4. 捕获差异
接下来,我们可以定期捕获本地内存差异,以检查随时间增加了提交内存的组件。为此,我们可以使用以下命令。
jcmd <PID> VM.native_memory detail.diff scale=MB > native_memory_diff
如果你注意到,这与步骤3中的命令相同,但有一个小差别。
detail.diff
从这里,我们可以得到与步骤3类似的报告,但与基线相比。
如果你仔细观察,你会发现在保留和已提交值旁边有带有**+**的值。这些增量与基线进行比较。
从步骤3(启动应用程序后立即)
Total: reserved=2590MB, committed=559MB
从步骤4
Total: reserved=3111MB +521MB, committed=1401MB +842MB
3111MB = 2590MB + 521MB
1401MB = 559MB + 842MB
通过这些详细信息,我们可以找到负责此内存增量的内存组件。
基于该详细信息,我们可以进一步采集相关转储、日志以进一步分析问题。
堆 → 捕获堆转储
类 → 捕获与加载的类相关的一些额外详细信息,如VM.classloader_stats、VM.class_hierarchy、GC.class_stats等。
线程 → 使用jstack捕获一组线程转储
GC → 在jcmd中捕获GC调试日志、堆转储(以检查无法访问的对象)或GC相关统计信息
同样,我们可以使用各种选项。你可以从jcmd -help中找到更多选项。
jcmd <PID> help
回到我的问题
我捕获了一些本地内存detail.diff,并发现内存利用率的原因是类。因此,我使用了以下命令来捕获有关JVM中类的更多详细信息。
jcmd <PID> VM.classloader_stats
jcmd <PID> VM.class_hierarchy
从这些报告中,我发现了以下类的原因,该类随着时间的推移迅速增长
|--org.mozilla.javascript.ScriptableObject/0x0000600002223710
| |--org.mozilla.javascript.IdScriptableObject/0x0000600002223710
| | |--org.mozilla.javascript.NativeMath/0x0000600002223710
| | |--org.mozilla.javascript.NativeDate/0x0000600002223710
| | |--org.mozilla.javascript.NativeCallSite/0x0000600002223710
| | |--org.mozilla.javascript.NativeError/0x0000600002223710
| | |--org.mozilla.javascript.NativeContinuation/0x0000600002223710
| | |--org.mozilla.javascript.BaseFunction/0x0000600002223710
| | | |--org.mozilla.javascript.IdFunctionObject/0x0000600002223710
| | | | |--org.mozilla.javascript.IdFunctionObjectES6/0x0000600002223710
| | | |--org.mozilla.javascript.NativeJavaMethod/0x0000600002223710
| | | |--org.mozilla.javascript.NativeFunction/0x0000600002223710
| | | | |--org.mozilla.javascript.gen.print_1976704/0x0000600006285fb0
| | | | |--org.mozilla.javascript.gen.print_1968885/0x00006000021f2740
| | | | |--org.mozilla.javascript.gen.print_1971296/0x0000600006260590
| | | | |--org.mozilla.javascript.gen.print_1963895/0x00006000022fe110
| | | | |--org.mozilla.javascript.gen.print_1979248/0x0000600006138bc0
| | | | |--org.mozilla.javascript.gen.print_1984688/0x000060000a256950
在几分钟内,这个类(org.mozilla.javascript.gen.print)的数量增加了~25000。经过进一步检查,我了解到这是Wso2产品+Java脚本中介程序(用于我的集成)的已知问题,可以通过使用Nashorn引擎而不是Rhino解决,如此处所述。
当跟踪本地内存(NMT)来解决与Java相关的内存问题时,特别是在负责组件不是堆的情况下,我认为这非常有帮助。
希望你喜欢这篇博客。
译自:https://medium.com/@selakapiumal/native-memory-tracking-in-a-java-based-application-502f7c841f9c
评论(0)