java8 "java.lang.OutOfMemoryError: Metaspace"

标签 java out-of-memory 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 

此外,该应用程序大量使用反射。我们还使用自定义类加载器。所有这些都在 java 7 中正常工作。

最佳答案

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

  • 当您多次将同一请求发送到服务器时,请检查在 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,然后查看加载的类的数量。在那里你可以监控堆以及元空间。但我会添加其他工具来密切监视元空间。
  • 此外,一旦您连接到 jvm,您可能需要拍摄堆快照并找出使用 OQL 加载的类。因此,在进行堆转储之前,请停止对服务器的请求,这样您就不会捕获任何进行中的请求/执行代码及其相关对象,但这不是必需的。因此,在多次运行同一组请求后,在 visualvm 中,在“监视器”空间中,单击右上角的“堆转储”。然后打开/加载快照,您将看到 OQL 控制台的选项。和您会在 permgen 分析下的右下面板上看到一些预定义的 OQL 查询。运行名为“类加载器加载的类直方图”的查询,我想这将给出每个类加载器加载的类的数量。您可以使用它来找出哪个类加载器正在加载类。

  • 选择 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
    
  • 然后尝试追踪元空间区域的增长。现在我们将使用 jconsole 和 java 有的新东西:jmc(java 任务控制)。您可以使用 jconsole 连接到 jvm(本地或远程),一旦连接到内存选项卡,您就可以监控那里的非堆增长,它应该有元空间和代码缓存以及压缩的类空间。
    现在连接

  • 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/

    相关文章:

    java - 如何检测 Java 8 直接内存剩余量?

    memory-leaks - 如何诊断 Java 8 元空间泄漏?

    java - 使用 GSON 将对象转换为 json 时出现 OutOfMemoryError

    java - 元空间和垃圾收集

    hibernate - 为什么类元空间在 Java 8 中会随着时间的推移而增加?

    java - 成功更新外观但仍有错误

    java - 在webview android中通过代理加载所有资源

    java - 如何将驻留在 Amazon ec2 实例中的 Tomcat 7 server.xml 文档库指向 Amazon S3 存储桶中的文件夹

    java - Hibernate 中的一对一共享主键映射可以是可选/空吗?

    java - Scala 中 OutOfMemoryError 的原因是什么?