【问题标题】:Lock contention on WebappClassLoader in Grails application under load负载下 Grails 应用程序中 WebappClassLoader 的锁定争用
【发布时间】:2014-03-12 19:49:44
【问题描述】:

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

我正在分析的设置如下:

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

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

我创建了一组针对应用程序触发的中等负载测试。我已经为我的本地设置找到了一种负载限制。在此限制之下,我对 Grails 应用程序提出以下意见:

  • 响应时间
  • 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

【问题讨论】:

    标签: java tomcat grails groovy classloader


    【解决方案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 结果集行转换为对象。这种锁定行为严重影响了运行大量线程和大型结果集的 tomcat,让我们除了重新启动之外别无选择。将上述设置与 Tomcat8 一起使用解决了该问题。

    我建议在 Tomcat7 上运行应用程序的用户升级到 Tomcat 8 并打开 ParallelWebappClassLoader 以获得更好的性能。

    【讨论】:

      猜你喜欢
      • 2018-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多