将我们的 Java 应用程序(在 Tomcat 上运行的服务)JRE 从 Java 7 切换到 Java 8 后,我们开始看到 java.lang.OutOfMemoryError: Metaspace
经过几天的高流量运行。
堆使用正常。在性能测试期间执行相同的代码流一段时间后,元空间会跳转。
元空间内存问题的可能原因是什么?
当前设置为:
-server -Xms8g -Xmx8g -XX:MaxMetaspaceSize=3200m -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=1000
-XX:+DisableExplicitGC -XX:+PrintGCDetails
-XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=7 -XX:NewSize=5004m
-XX:MaxNewSize=5004m -XX:MaxTenuringThreshold=12
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintFlagsFinal
-XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution
-XX:+PrintGCCause -XX:+PrintAdaptiveSizePolicy
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=3 -XX:GCLogFileSize=200M
此外,该应用程序大量使用反射。我们还使用自定义类加载器。所有这些都在 java 7 中正常工作。
最佳答案
我假设您可以在一段时间内使用相同的请求(请求集)创建问题。
定义 MaxMetaspaceSize 是一件好事,否则应用程序将使用 native 内存,直到它用完为止。
但我将从以下步骤开始:
我将提到本地的步骤,但对于远程附加 JMX,您应该将以下内容添加到应用程序的 JVM 参数并启动它并在端口 9999 和 -XX:+UnlockDiagnosticVMOptions 上远程连接。
-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -XX:+UnlockDiagnosticVMOptions
一旦将visualvm(jvisualvm)连接到JVM,点击monitor,然后查看加载的类的数量。在那里你可以监控堆以及元空间。但我会添加其他工具来密切监视元空间。
选择 map (排序( map (heap.objects('java.lang.ClassLoader'),
'{loader: it, count: it.classes.elementCount }'), 'lhs.count < rhs.count'),
'toHtml(it) + ""')
但是上面名为“类加载器加载的类”的查询会很慢,它实际上会显示每个类加载器加载的类。
select { loader: cl,
classes: filter(map(cl.classes.elementData, 'it'), 'it != null') }
from instanceof java.lang.ClassLoader cl
现在连接
jmc
连接到虚拟机,然后在连接后单击右上角的 JMC 中的“诊断命令”。由于我们启用了 UnlockDiagnosticVMOptions ,因此可以执行 GC.class_stats 。您可能希望通过显示所有列运行它并在 csv 中打印。所以命令看起来像:
GC.class_stats -all=true -csv=true
然后你可以比较不同时期的类统计数据,找出哪些类导致问题(元空间增长)或哪些类在元空间中有相关信息(方法/方法数据)。如何分析当时收集的 csv 输出:好吧,我会将该 csv 加载到数据库或其他地方的两个类似表(代表 csv)中,以比较 GC.class_stats csv 输出,我可以在其中运行一些 SQL 或任何其他分析工具。这样可以更好地了解元空间中究竟增长了什么。
GC 类统计信息包含以下列:
Index,Super,InstSize,InstCount,InstBytes,Mirror,KlassBytes,K_secondary_supers,VTab,ITab,OopMap,IK_methods,IK_method_ordering,IK_default_methods,IK_default_vtable_indices,IK_local_interfaces,IK_transitive_interfaces,IK_fields,IK_inner_classes,IK_signers,class_annotations,class_type_annotations,fields_annotations,fields_type_annotations,methods_annotations,methods_parameter_annotations,methods_type_annotations,methods_default_annotations,annotations,Cp,CpTags,CpCache,CpOperands,CpRefMap,CpAll,MethodCount,MethodBytes,ConstMethod,MethodData,StackMap,Bytecodes,MethodAll,ROAll,RWAll,Total,ClassName,ClassLoader
希望能帮助到你。此外,如果它不会在 1.7 中导致任何泄漏,则该错误可能出现在 Java 8 中。
如果任何人持有对类加载器的任何引用,这些类也不会从元空间中卸载。如果你知道你的类加载器应该被 GC 并且没有人应该持有对你的类加载器的引用,你可以回到visualvm 中的堆转储并单击类加载器实例并右键单击以找到“最近的 GC 根”,它会告诉你您持有对类加载器的引用。
关于java8 "java.lang.OutOfMemoryError: Metaspace",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36051813/