在加载下的Grails应用程序中锁定WebappClassLoader上的争用

时间:2014-03-12 19:49:44

标签: java tomcat grails groovy classloader

我正在使用JProfiler和JConsole对Grails Web应用程序进行概要分析,并观察到一种我无法完全理解的现象。

我正在分析的设置如下:

  • 使用maven构建的Grails Web应用程序作为WAR并部署到Tomcat。
  • 单个Solr实例
  • Tomcat,线程池大小为100个HTTP工作线程,BIO连接器

应用程序几乎每个请求都会调用Solr。没有数据库,也没有其他后端交互。 Ehcache用于缓存。

我创建了一组针对应用程序触发的中等负载测试。我已经为我的本地设置找出了一种负载限制。低于该限制,我对Grails应用程序进行了以下观察:

  • 响应时间< 500ms的
  • HTTP线程主要花费时间与净I / O,有点可运行和等待时间以及非常少的块时间
  • CPU约70%

当我将负载提高超过上限时,图像会发生剧烈变化:

  • 响应时间增加到几秒
  • HTTP线程主要处于阻塞或可运行状态,非常少的网络I / O和等待时间
  • CPU似乎在40%到80%之间“跳跃”

现在虽然完全可以理解在高负载下事情变慢但我非常担心超出负载限制锁定争用的事实并且花费了大量时间来阻塞。我调查了监视器的历史记录,特别是发现了很多与以下类似的事件:

  • 监控班级org.apache.catalina.loader.WebappClassLoader
  • 等待和拥有线程是一些HTTP工作者
  • 用于监控显示器的时间很长(定期约2秒)
  • 这些事件也发生在负载限制之下,但它们很少见,并且没有这种破坏性影响
  • 拥有和等待线程的堆栈跟踪非常不同,下面是一个示例。

过滤了拥有线程的堆栈跟踪

groovy.lang.GroovyClassLoader.loadClass(java.lang.String)
Script1.$createCallSiteArray()
Script1.$getCallSiteArray()
Script1.__$swapInit()
Script1.<clinit>()
groovy.lang.Script$evaluate.callCurrent(groovy.lang.GroovyObject, java.lang.Object)
gsp_some_gsp_gsp.run()
org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateRenderer.render(...)
Script1.call(java.lang.Object, java.lang.Object[ ])
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(...)

等待线程的过滤堆栈跟踪:

groovy.lang.Script$evaluate.callCurrent(groovy.lang.GroovyObject, java.lang.Object)
gsp_some_gsp_gsp.run()
org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateRenderer.render(...)
Script1.call(java.lang.Object, java.lang.Object[ ])
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(...)
gsp_some_other_gsp_gsp$_run_closure2_closure5.doCall()
org.codehaus.groovy.grails.web.taglib.GroovyPageTagBody.call()
java_util_concurrent_Callable$call$53.call(java.lang.Object)
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(...)

另一个观察结果(无论负载限制如何)是,加载类的总数不断增加。 (也会发生常规类卸载,因此到目前为止PermGen不是问题,但似乎不断生成新类。)

我的问题是为什么会发生WebappClassLoader上的这种阻塞。这种正常行为是否超过负载限制阻塞线程堆叠的事实只是机器过载的标志?这与Groovy或Grails或Tomcat类加载有关吗?有什么可以做的吗?

有关该系统的一些其他详细信息:

java version "1.7.0_51"
OpenJDK Runtime Environment (IcedTea 2.4.4) (7u51-2.4.4-0ubuntu0.12.04.2)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)
Grails version 2.3.4
Groovy version 2.1.9
Ehcache 2.4.6
Tomcat version 7.0.26
Ubuntu 12.04
curl-loader for load tests

1 个答案:

答案 0 :(得分:1)

可以通过升级到Tomcat 8.0.16或更高版本并将Loader类设置为org.apache.catalina.loader.ParallelWebappClassLoader来解决此问题。

在[TOMCAT_HOME] /conf/context.xml中,添加以下行并重新启动

<Loader loaderClass="org.apache.catalina.loader.ParallelWebappClassLoader" />

我们遇到了类似的问题,Hibernate和Spring JDBC会尝试将db结果集行转换为对象。这种锁定行为严重影响了tomcats运行大量具有大型结果集的线程,除了重启之外别无选择。使用Tomcat8的上述设置解决了这个问题。

我建议在Tomcat7上运行应用程序的用户在启用ParallelWebappClassLoader的情况下升级到Tomcat 8,以获得更好的性能。