java8“java.lang.OutOfMemoryError:Metaspace”

在将我们的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 

该应用程序也大量使用reflection。 我们还使用自定义类加载器。 所有这些都在java 7中运行良好。

我假设您可以在一段时间内使用相同的请求(请求集)创建问题。 定义MaxMetaspaceSize是一件好事,否则应用程序将使用本机内存,直到它用完为止。 但我将从以下步骤开始:

  1. 当您多次将其发送到服务器时,请检查JVM中加载的类的数量是否会随着相同的请求而增长。 如果是,您可能正在创建动态类,这将导致在元空间中加载的类增长。 那么如何检查加载的类的数量,您可以使用visualvm使用JMX连接到服务器或在本地运行来模拟。 我将提到本地的步骤,但是对于远程连接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,然后查看加载的类数。 在那里,您可以监视堆以及元空间。 但我会添加其他工具来密切监控元空间。

  1. 此外,一旦连接到jvm,您可能需要获取堆快照并找出使用OQL加载的类。 因此,在您进行堆转储之前,请停止对服务器的请求,因此您不会捕获任何机上请求/执行代码及其关联对象,但这不是必需的。 因此,在visualvm内,在“监视器”空间中多次运行相同的请求集后,单击右上角的“堆转储”。然后打开/加载快照,您将看到OQL控制台的选项。你会在permgen分析下的右下方面板上看到一些预定义的OQL查询。运行名为“classloader loaded class histogram”的查询,我想这将给出每个类加载器加载的类的数量。你可以用它来找出哪个类加载器正在加载课程。

选择map(sort(map(heap.objects(’java.lang.ClassLoader’),'{loader:it,count:it.classes.elementCount}’),’lhs.count “’)

但上面命名为“classloader loaded class”的查询将会很慢,这将实际显示每个类加载器加载的类。

 select { loader: cl, classes: filter(map(cl.classes.elementData, 'it'), 'it != null') } from instanceof java.lang.ClassLoader cl 
  1. 然后尝试追踪元空间区域的增长。 现在我们将使用jconsole和java拥有的新东西:jmc(java mission control)。 您可以使用jconsole连接到jvm(本地或远程),一旦连接到内存选项卡,您就可以监视那里的非堆增长,这应该具有元空间和代码缓存以及压缩类空间。 现在连接

JMC

要连接到VM,然后在连接后单击右侧顶部的JMC中的“诊断命令”。 由于我们已启用UnlockDiagnosticVMOptions,因此可以执行GC.class_stats。 您可能希望使用show all columns运行它并在csv中打印。 所以命令看起来像:

 GC.class_stats -all=true -csv=true 

然后,您可以比较不同时期的类统计信息,并找出哪些类导致问题(元空间增长)或哪些类在元空间中具有相关信息(方法/方法数据)。 如何分析在时间收集的csv输出:我会把csv加载到数据库或其他地方的两个相似的表(代表csv)中以比较GC.class_stats csv输出,我可以运行一些SQL或任何其他分析工具。 这样可以更好地了解元空间中的增长情况。 GC类统计信息包含以下列:

指数,超级,InstSize,InstCount,InstBytes,镜子,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,注释,CP,CpTags,CpCache,CpOperands,CpRefMap,CpAll,MethodCount,MethodBytes,ConstMethod,MethodData,StackMap,字节码,MethodAll,ROAll,RWAll,道达尔,类名,类加载器

希望能帮助到你。 此外,如果它不会导致1.7中的任何泄漏,它似乎可能在Java 8中。

如果任何人持有对类加载器的任何引用,那么这些类也不会从元空间中卸载。 如果您知道您的类加载器应该是GCed并且没有人应该保留对类加载器的引用,您可以返回到visualvm中的堆转储并单击类加载器实例并右键单击以查找“最近的GC根”,这将告诉您你持有类加载器的引用。

我们有类似的问题,根本原因是60K类文件被加载到元空间内存,但没有任何东西被卸载。添加到JVM下面arg修复了问题。

 -Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true 

https://issues.apache.org/jira/browse/CXF-2939

希望这可以帮助。