tomcat 类加载器性能问题

tomcat classloader performance issue

我们的基准测试发现了 Apache Tomcat 7.0.59 中的一个漏洞。当服务器达到其性能极限时,其大部分线程被 ClassLoader 锁定。

阻塞线程的堆栈跟踪类似于此示例:

"http-bio-4504-exec-500" Id=2335 BLOCKED on java.util.jar.JarFile@464f9f8 owned by "[1432628598653] POST /services/signin HTTP/1.0" Id=1990
    at java.util.zip.ZipFile.getEntry(ZipFile.java:304)
    -  blocked on java.util.jar.JarFile@464f9f8
    at java.util.jar.JarFile.getEntry(JarFile.java:226)
    at java.util.jar.JarFile.getJarEntry(JarFile.java:209)
    at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:840)
    at sun.misc.URLClassPath$JarLoader.findResource(URLClassPath.java:818)
    at sun.misc.URLClassPath.next(URLClassPath.java:226)
    at sun.misc.URLClassPath.hasMoreElements(URLClassPath.java:236)
    at java.net.URLClassLoader.run(URLClassLoader.java:583)
    at java.net.URLClassLoader.run(URLClassLoader.java:581)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.next(URLClassLoader.java:580)
    at java.net.URLClassLoader.hasMoreElements(URLClassLoader.java:605)
    at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java:45)
    at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java:54)
    at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java:45)
    at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java:54)
    at com.sun.xml.ws.util.ServiceFinder$LazyIterator.hasNext(ServiceFinder.java:443)
    at com.sun.xml.ws.util.ServiceFinder$CompositeIterator.hasNext(ServiceFinder.java:390)
    at com.sun.xml.ws.api.message.saaj.SAAJFactory.getMessageFactory(SAAJFactory.java:96)
    at com.sun.xml.ws.api.SOAPVersion.getMessageFactory(SOAPVersion.java:221)
    at com.sun.xml.ws.api.message.saaj.SAAJFactory.readAsSOAPMessage(SAAJFactory.java:275)
    at com.sun.xml.ws.api.message.saaj.SAAJFactory.readAsSAAJ(SAAJFactory.java:205)
    at com.sun.xml.ws.api.message.saaj.SAAJFactory.read(SAAJFactory.java:194)
    at com.sun.xml.ws.message.AbstractMessageImpl.toSAAJ(AbstractMessageImpl.java:199)
    at com.sun.xml.ws.api.message.MessageWrapper.readAsSOAPMessage(MessageWrapper.java:160)
    at com.sun.xml.ws.handler.SOAPMessageContextImpl.getMessage(SOAPMessageContextImpl.java:86)

阻塞线程运行在同一个地方

"[1432628598653] POST /services/signin HTTP/1.0" Id=1990 RUNNABLE
    at java.util.zip.ZipFile.getEntry(ZipFile.java:304)
    -  locked java.util.jar.JarFile@464f9f8
    at java.util.jar.JarFile.getEntry(JarFile.java:226)
    at java.util.jar.JarFile.getJarEntry(JarFile.java:209)
    at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:840)
    at sun.misc.URLClassPath$JarLoader.findResource(URLClassPath.java:818)
    at sun.misc.URLClassPath.next(URLClassPath.java:226)
    at sun.misc.URLClassPath.hasMoreElements(URLClassPath.java:236)
    at java.net.URLClassLoader.run(URLClassLoader.java:583)
    at java.net.URLClassLoader.run(URLClassLoader.java:581)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.next(URLClassLoader.java:580)
    at java.net.URLClassLoader.hasMoreElements(URLClassLoader.java:605)
    at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java:45)
    at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java:54)
    at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java:45)
    at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java:54)
    at com.sun.xml.ws.util.ServiceFinder$LazyIterator.hasNext(ServiceFinder.java:443)
    at com.sun.xml.ws.util.ServiceFinder$CompositeIterator.hasNext(ServiceFinder.java:390)
    at com.sun.xml.ws.api.message.saaj.SAAJFactory.read(SAAJFactory.java:190)
    at com.sun.xml.ws.message.AbstractMessageImpl.toSAAJ(AbstractMessageImpl.java:199)
    at com.sun.xml.ws.api.message.MessageWrapper.readAsSOAPMessage(MessageWrapper.java:160)
    at com.sun.xml.ws.handler.SOAPMessageContextImpl.getMessage(SOAPMessageContextImpl.java:86)

我们的应用程序是否以错误的方式执行某些操作?是否有避免在类加载器中阻塞的解决方法?

问题已通过以下 hack 完全解决:

  1. 我在 JVM 参数中添加了 -Djaxp.debug=true。
  2. 然后我在日志中找到了 JAX-WS 正在寻找的所有 classes。
  3. 我为每个提到的 class 添加了系统 属性,它使 ServiceFinder 忽略了在 class 路径中搜索 service.properties 文件。

我最终的 JVM args 看起来像:

-Djavax.xml.soap.MetaFactory=com.sun.xml.internal.messaging.saaj.soap.SAAJMetaFactoryImpl
-Djavax.xml.datatype.DatatypeFactory=com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl
-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
-Djavax.xml.stream.XMLInputFactory=com.ctc.wstx.stax.WstxInputFactory
-Djavax.xml.stream.XMLOutputFactory=com.ctc.wstx.stax.WstxOutputFactory
-Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl