旧的 JaxB 和 JDK 8 元空间内存不足问题

Old JaxB and JDK8 Metaspace OutOfMemory Issue

我们正在开发 10 多年以来开发的业务应用程序(100 万+ LOC)。在切换到 JDK8 时,我们遇到了 JDK8 元空间的问题。这似乎与 com.sun.xml.ws:webservices-rt:1.4 (Metro 1.4) 中引用的 JaxB 版本有关。由于应用程序中的密集链接和通过 JaxB 的 classes/instances 遗留创建,动态切换旧库并不简单。

目前我们正在研究这个问题。我们创建了一个重现此行为的示例程序:

import java.io.ByteArrayInputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class X
{
  private static final String XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><x test=\"test\" />";

  @XmlAttribute
  String test;

  public static void main( String[] args ) throws JAXBException, InterruptedException
  {
    System.out.println("start");

    while ( true )
    {
      JAXBContext jc = JAXBContext.newInstance( X.class );
      Unmarshaller unmarshaller = jc.createUnmarshaller();
      X object = (X) unmarshaller.unmarshal( new ByteArrayInputStream( XML.getBytes() ) );
      System.out.println( object.test );
    }
  }
}

JDK7 保持 PermGenSpace 干净。 (用 16M PermGen 模拟) Memory of run with JDK7

使用 JDK8 的应用程序 运行s 缓慢到 OOM 异常。 VisualVM 捕获异常并将进程 运行ning 保持在最大可用元空间上。即使在这里,它也会在 运行 达到最大值后卡住。 (用16M元空间模拟) Memory of run with JDK8

有没有人知道如何获得垃圾收集器的遗留行为,这样我们就不会 运行 陷入那些内存不足的问题?或者您对如何处理此问题有任何其他想法?

谢谢。

编辑1: 运行参数JDK7:

-XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:MaxPermSize=16M -XX:PermSize=1M -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError

=> 没有创建堆转储

运行参数JDK8:

-XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:MaxMetaspaceSize=16M -XX:MetaspaceSize=1M -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError

=> 在 运行ning 时生成堆转储。

VisualVM 的可用内存没有显示真正的最大元空间值。如果没有限制,元空间会不断增加,直到超出内存。

编辑 2:

我已经尝试了所有适用于 JDK8 的垃圾收集器。他们都有同样的问题。

编辑 3:

由于 JAXB 与我们应用程序的多个模块之间的严重耦合,在我们的实际应用程序中很难通过交换库来解决。因此,短 运行 需要修复垃圾收集器行为。在长期 运行 中,适当的修复已经计划好了。

我们解决了当前的问题,直到能够使用以下 VM 参数修复应用程序中的所有问题:

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

我希望这会帮助其他有类似问题的人...

JAXBContext.newInstance() 应该使用一次来为您的 class 解组创建上下文。否则它将用完您的 permgen 或元空间。

这是 Gary 正在谈论的解决方案,这比仅仅设置一个标志要好(因为甚至 JAXB guys 建议将其设置为单例...)

private static Map<class<?>, JAXBContext> contextStore = new ConcurrentHashMap<class<?>, JAXBContext>();
... 
protected static JAXBContext getContextInstance(Class<?> objectClass) throws JAXBException{
  JAXBContext context = contextStore.get(objectClass);
  if (context==null){
    context = JAXBContext.newInstance(objectClass);
    contextStore.put(objectClass, context);
  }
  return context;
}

//using it like this:
JAXBContext context = getContextInstance(objectClass);

可以找到 JAXB-564 错误 + 修复 here. Inspired by the long gone blog of scorgar