什么可能导致全局Tomcat / JVM减速?

时间:2013-02-14 13:58:10

标签: java performance tomcat7

我遇到了一个奇怪但严重的问题,在Tomcat 7 / Java 7上运行了几个(大约15个)Java EE-ish Web应用程序实例(Hibernate 4 + Spring + Quartz + JSF + Facelets + Richfaces)。

系统运行良好,但是在大量不同的时间之后,应用程序的所有实例同时突然受到响应时间上升的影响。基本上应用程序仍然有效,但响应时间大约高出三倍。

这是两个图表,显示两个特定的短工作流程/操作的响应时间(登录,研讨会的访问列表,ajax刷新此列表,注销;下面的行只是ajax刷新的请求时间)应用程序的两个示例实例:

Response times of context 1 Resoinse times of context 2

正如您可以看到应用程序的两个实例"爆炸"在同一时间,保持缓慢。重新启动服务器后,所有内容都恢复正常。应用程序的所有实例"爆炸"同时进行。

我们将会话数据存储到数据库并将其用于群集。我们检查了会话大小和数量,两者都相当低(这意味着在其他服务器上我们有时会有更大和更多的会话)。群集中的另一个Tomcat通常会保持快几个小时,在这个随机的时间之后它也会死掉#34;。我们使用jconsole检查堆大小,主堆保持在2.5到1 GB大小之间,db连接池基本上充满了空闲连接,以及线程池。最大堆大小为5 GB,还有大量可用的perm gen空间。负荷不是特别高;主CPU只有大约5%的负载。服务器不交换。它也没有硬件问题,因为我们将应用程序另外部署到VM,其中问题保持不变。

我不知道在哪里看,我没有想法。有人知道在哪里看?

2013-02-21更新:新数据!

我在应用程序中添加了两个计时跟踪。至于测量:监视系统调用执行两个任务的servlet,测量服务器上每个任务的执行时间,并写入作为响应的时间。这些值由监控系统记录。

我有几个有趣的新事实:应用程序的热重新部署导致当前Tomcat上的这个单个实例疯狂。这似乎也会影响原始CPU计算性能(见下文)。这种个别情境爆炸不同于随机发生的整体情境爆炸。

现在有些数据:

Diagram 3 Diagram 4

首先是个别行:

  1. 淡蓝色是小型工作流程的总执行时间(详见上文),在客户端上测量
  2. 红色是"部分"浅蓝色,是在客户端上测量该工作流程的特殊步骤所花费的时间
  3. 深蓝色是在应用程序中测量的,包括通过Hibernate从DB读取实体列表并迭代该列表,获取惰性集合和惰性实体。
  4. Green是一个使用浮点和整数运算的小型CPU基准测试。据我所见,没有对象分配,所以没有垃圾。
  5. 现在针对爆炸的各个阶段:我用三个黑点标记每个图像。第一个是"小"爆炸或多或少只有一个应用程序实例 - 在Inst1中它会跳转(特别是在红线中可见),而Inst2在或多或少的情况下保持平静。

    在这次小爆炸之后,大爆炸"发生并且该Tomcat上的所有应用程序实例都会爆炸(第2个点)。请注意,此爆炸会影响所有高级操作(请求处理,数据库访问),但 CPU基准。它在两个系统中保持低水平。

    之后,我通过触摸context.xml文件热重新部署了Inst1。正如我之前所说的那样,这个例子现在从爆炸变为完全被破坏(浅蓝色线在图表之外 - 大约是18秒)。请注意a)此重新部署根本不会影响Inst2,以及b)Inst1的原始数据库访问如何也不会受到影响 - 但是CPU突然似乎变得更慢了!。我说这很疯狂。

    更新更新 在取消部署应用程序时,Tomcat的防漏侦听器不会抱怨陈旧的ThreadLocals或Threads。显然有一些清理问题(我认为这与大爆炸没有直接关系),但Tomcat对我没有任何暗示。

    2013-02-25更新:应用程序环境和Quartz计划

    应用程序环境不是很复杂。除了网络组件(我不太了解那些),基本上有一个应用服务器(Linux)和两个数据库服务器(MySQL 5和MSSQL 2008)。主要负载在MSSQL服务器上,另一个仅用作存储会话的位置。

    应用程序服务器运行Apache作为两个Tomcats之间的负载平衡器。因此,我们在同一硬件上运行两个JVM(两个Tomcat 实例)。我们使用此配置实际上不平衡负载,因为应用程序服务器能够正常运行应用程序(它已经多年来一直运行),但是可以在不停机的情况下启用小型应用程序更新。有问题的Web应用程序部署为不同客户的单独上下文,每个Tomcat约15个上下文。 (我觉得混淆了#34;实例""背景"在我的帖子中 - 在办公室里,他们经常使用同义词,我们通常神奇地知道同事在说什么我的坏,我真的很抱歉。)

    用更好的措辞来澄清情况:我发布的图表显示了同一个JVM上同一个应用程序的两个不同上下文的响应时间。大爆炸会影响一个JVM上的所有上下文但不会发生在另一个JVM上(Tomcats爆炸的顺序是随机的btw)。在热重新部署之后,一个Tomcat实例上的一个上下文变得疯狂(带有所有有趣的副作用,就像上下文中看起来速度较慢的CPU)。

    系统的总体负载相当低。它是一个内部核心业务相关软件,同时拥有约30个活跃用户。特定于应用程序的请求(服务器触摸)目前大约是每分钟130个。单个请求的数量很少,但是请求本身通常需要数百个选择到数据库,因此它们相当昂贵。但通常一切都完全可以接受。该应用程序也不会创建大型无限缓存 - 某些查找数据会被缓存,但只能在很短的时间内完成。

    上面我写道,能够运行应用程序的服务器已经好几年了。我知道找到问题的最佳方法是找出第一次出现问题的确切时间,并查看在此时间范围内(在应用程序本身,相关库或基础架构中)已更改的内容,但问题是我们不知道问题何时首次发生。只是让我们称之为次优(在缺席的意义上)应用程序监控...: - /

    我们排除了某些方面,但应用程序在过去几个月中已经多次更新,因此我们例如不能简单地部署旧版本。没有功能变化的最大更新是从JSP切换到Facelets。但仍然,"东西"必须是所有问题的原因,但我不知道为什么Facelets应该影响纯DB查询时间。

    石英

    至于石英时间表:总共有8个工作岗位。他们中的大多数每天只运行一次,并且与大容量数据同步有关(绝对不是"大"因为"大数据大";它只是比... averate用户通过他平常的日常工作看到)。然而,这些工作当然是在夜间运行,问题发生在白天。我在这里省略了一份详细的工作清单(如果有益,我可以提供更多详细信息)。工作'源代码在过去几个月没有被修改过。我已经检查了爆炸是否与工作一致 - 但结果最多也是不确定的。我实际上说他们没有对齐,但由于有几个工作每分钟都在运行,我现在还不能排除它。在我看来,每分钟运行的实际工作相当轻,他们通常会检查数据是否可用(在不同的来源,数据库,外部系统,电子邮件帐户),如果是,请将其写入数据库或将其推送到另一个系统

    然而,我目前正在启用单个作业执行的记录,以便我可以准确地看到每个单个作业执行的开始和结束时间戳。也许这提供了更多的见解。

    2013-02-28更新:JSF阶段和时间

    我手动向应用程序添加了一个JSF phae侦听器。我执行了一个示例调用(ajax刷新),这就是我所拥有的(左:正常运行Tomcat实例,右:Big Bang之后的Tomcat实例 - 这些数字几乎同时来自两个Tomcats,并且以毫秒为单位) ):

    1. RESTORE_VIEW:17 vs 46
    2. APPLY_REQUEST_VALUES:170 vs 486
    3. PROCESS_VALIDATIONS:78 vs 321
    4. UPDATE_MODEL_VALUES:75 vs 307
    5. RENDER_RESPONSE:1059 vs 4162
    6. ajax刷新本身属于搜索表单及其搜索结果。在应用程序的最外请求过滤器和Web流程开始工作之间还存在另一个延迟:FlowExecutionListenerAdapter用于测量Web流的某些阶段所花费的时间。该监听器报告1405毫秒的“#34;请求提交" (就我所知的第一个网络流程事件而言)对于未爆炸的Tomcat的完整请求总计1632毫秒,因此我估计大约200毫秒的开销。
      但是在爆炸性的Tomcat上,它报告了5332毫秒的请求提交(意味着所有JSF阶段都发生在这5秒内),总持续时间为7105毫秒,因此我们在网络流量以外的所有内容上的开销几乎达到2秒#39;提交的请求。
      在我的测量过滤器下面,过滤器链包含org.ajax4jsf.webapp.BaseFilter,然后调用Spring servlet。

      2013-06-05更新:过去几周发生的所有事情

      一个小而且相当晚的更新...应用程序性能在一段时间后仍然很糟糕,并且行为仍然不稳定。分析还没有多少帮助,它只是产生了大量难以剖析的数据。 (尝试在性能数据上探讨或描述生产系统......叹息)我们进行了多次测试(剥离软件的某些部分,取消部署其他应用程序等),并且实际上有一些改进会影响整个应用程序。我们EntityManager的默认刷新模式为AUTO,在视图渲染期间发出大量提取和选择,始终包括检查是否需要刷新。
      因此,我们构建了一个JSF阶段侦听器,在COMMIT期间将刷新模式设置为RENDER_RESPONSE。这提高了整体性能很多,似乎在一定程度上缓解了这些问题。

      然而,我们的应用程序监控在一些tomcat实例的某些上下文中不断产生完全疯狂的结果和性能。就像一个应该在一秒钟内完成的动作(实际上它在部署后完成),现在需要超过四秒钟。 (这些数字由浏览器中的手动计时支持,因此不是导致问题的监控)。

      例如,见下图:
      Diagram

      此图显示了两个运行相同上下文的tomcat实例(意思是相同的db,相同的配置,相同的jar)。蓝线再次是纯DB读取操作所花费的时间量(获取实体列表,迭代它们,懒惰地获取集合和相关数据)。绿松石色和红色线分别通过渲染几个视图和进行ajax刷新来测量。绿松石色和红色的两个请求所呈现的数据与蓝线的查询大致相同。

      现在在实例1(右)的0700左右,纯数据库时间的这种巨大增长似乎也影响了实际的渲染响应时间,但仅限于tomcat 1. Tomcat 0在很大程度上不受此影响,所以它不能由数据库服务器或网络引起,两个tomcats在同一物理硬件上运行。它必须是Java域中的软件问题。

      在我上次测试期间,我发现了一些有趣的内容:所有回复都包含标题" X-Powered-By:JSF / 1.2,JSF / 1.2"。一些(WebFlow产生的重定向响应)甚至有" JSF / 1.2"在那里三次 我查找了设置这些标题的代码部分,并且第一次设置此标题是由此堆栈引起的:

      ... at org.ajax4jsf.webapp.FilterServletResponseWrapper.addHeader(FilterServletResponseWrapper.java:384)
      at com.sun.faces.context.ExternalContextImpl.<init>(ExternalContextImpl.java:131)
      at com.sun.faces.context.FacesContextFactoryImpl.getFacesContext(FacesContextFactoryImpl.java:108)
      at org.springframework.faces.webflow.FlowFacesContext.newInstance(FlowFacesContext.java:81)
      at org.springframework.faces.webflow.FlowFacesContextLifecycleListener.requestSubmitted(FlowFacesContextLifecycleListener.java:37)
      at org.springframework.webflow.engine.impl.FlowExecutionListeners.fireRequestSubmitted(FlowExecutionListeners.java:89)
      at org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:255)
      at org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:169)
      at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:183)
      at org.springframework.webflow.mvc.servlet.FlowController.handleRequest(FlowController.java:174)
      at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)
      at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
      ... several thousands ;) more
      

      此标头第二次由

      设置
      at org.ajax4jsf.webapp.FilterServletResponseWrapper.addHeader(FilterServletResponseWrapper.java:384)   
      at com.sun.faces.context.ExternalContextImpl.<init>(ExternalContextImpl.java:131)   
      at com.sun.faces.context.FacesContextFactoryImpl.getFacesContext(FacesContextFactoryImpl.java:108)   
      at org.springframework.faces.webflow.FacesContextHelper.getFacesContext(FacesContextHelper.java:46)   
      at org.springframework.faces.richfaces.RichFacesAjaxHandler.isAjaxRequestInternal(RichFacesAjaxHandler.java:55)   
      at org.springframework.js.ajax.AbstractAjaxHandler.isAjaxRequest(AbstractAjaxHandler.java:19)   
      at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.createServletExternalContext(FlowHandlerAdapter.java:216)   
      at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:182)   
      at org.springframework.webflow.mvc.servlet.FlowController.handleRequest(FlowController.java:174)   
      at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)   
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)   
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)   
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)   
      at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)   
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
      

      我不知道这是否表明存在问题,但我没有注意到在我们的任何服务器上运行的其他应用程序,所以这也可能提供一些提示。我真的不知道那个框架代码在做什么(不可否认我还没有深入研究它)......也许有人有想法?还是我陷入了死胡同?

      附录

      我的CPU基准代码包含一个循环,它计算Math.tan并使用结果值修改servlet实例上的某些字段(没有volatile / synchronized),其次执行几个原始整数计算。我知道,这并不是非常复杂,但是......它似乎在图表中显示了一些东西,但是我不确定它显示的是什么。我进行了现场更新,以防止HotSpot优化掉我所有宝贵的代码;)

          long time2 = System.nanoTime();
          for (int i = 0; i < 5000000; i++) {
              double tan = Math.tan(i);
              if (tan < 0) {
                  this.l1++;
              } else {
                  this.l2++;
              }
          }
      
          for (int i = 1; i < 7500; i++) {
              int n = i;
              while (n != 1) {
                  this.steps++;
                  if (n % 2 == 0) {
                      n /= 2;
                  } else {
                      n = n * 3 + 1;
                  }
              }
          }
          // This execution time is written to the client.
          time2 = System.nanoTime() - time2;
      

7 个答案:

答案 0 :(得分:43)

解决方案

增加代码缓存的最大大小:

-XX:ReservedCodeCacheSize=256m

背景

我们正在使用在Tomcat 7和Java 1.7.0_15上运行的ColdFusion 10。我们的症状与您的相似。有时,服务器上的响应时间和CPU使用率会因为没有明显原因而大幅上升。似乎CPU变慢了。唯一的解决方案是重启ColdFusion(和Tomcat)。

初步分析

我首先查看了内存使用情况和垃圾收集器日志。那里没有任何东西可以解释我们的问题。

我的下一步是每小时安排一次堆转储,并使用VisualVM定期执行采样。目标是在减速之前和之后获取数据,以便进行比较。我设法做到了。

抽样中有一个功能突出:coldfusion.runtime.ConcurrentReferenceHashMap中的get()。与之前的很少相比,在经济放缓之后花了很多时间。我花了一些时间来理解函数是如何工作的,并且开发了一个理论,即哈希函数可能存在一些问题,导致一些巨大的存储桶。使用堆转储,我能够看到最大的桶只包含6个元素,所以我放弃了这个理论。

代码缓存

当我阅读&#34; Java Performance:The Definitive Guide&#34;时,我终于走上正轨。它有一章关于JIT编译器,它讨论了我之前没有听说过的代码缓存。

禁用编译器

当监视执行的编译数量(使用jstat监视)和代码缓存的大小(使用VisualVM的内存池插件监视)时,我看到大小增加到最大大小(默认情况下为48 MB)我们的环境 - 默认值因Java版本和Java编译器而异。当代码缓存已满时,JIT编译器被关闭。我已经读过&#34; CodeCache已满。编译器已被禁用。&#34;当发生这种情况时应该打印但是我没有看到那个消息;也许我们使用的版本没有该消息。我知道编译器已关闭,因为执行的编译数量停止增加。

去优化继续

JIT编译器可以对先前编译的函数进行优化,这将使得函数再次被解释器执行(除非该函数被改进的编译替换)。可以对去优化的函数进行垃圾回收以释放代码高速缓存中的空间。

由于某种原因,即使没有编译任何内容来替换它们,功能仍然会被去优化。代码缓存中可以使用越来越多的内存,但JIT编译器没有重新启动。

当我们遇到减速时,我从来没有启用-XX:+ PrintCompilation但是我很确定我会看到ConcurrentReferenceHashMap.get()或它所依赖的函数,在那时被去优化。

结果

我们没有看到任何减速因为我们将代码高速缓存的最大大小增加到256 MB,我们也看到了一般的性能提升。我们的代码缓存目前有110 MB。

答案 1 :(得分:9)

首先,我要说你在抓住有关问题的详细事实方面做得非常出色;我真的很喜欢你如何清楚地知道你所知道的以及你在猜测什么 - 这真的很有帮助。

编辑1 在上下文与实例

的更新后进行大量编辑

我们可以排除:

  • GC(会影响CPU基准测试服务线程并加速主CPU)
  • Quartz作业(会影响Tomcats或CPU基准测试)
  • 数据库(会影响两个Tomcats)
  • 网络数据包风暴和类似风暴(会影响两只雄猫)

我相信您正在遭受JVM中某处延迟的增加。延迟是线程正在等待(同步)来自某处的响应的地方 - 它增加了您的servlet响应时间,但不会给CPU带来任何成本。典型的延迟是由:

引起的
  • 网络电话,包括
    • JDBC
    • EJB或RMI
    • JNDI
    • DNS
    • 文件共享
  • 磁盘读写
  • 线程
    • 从队列中读取(有时写入)
    • synchronized方法或块
    • futures
    • Thread.join()
    • Object.wait()
    • Thread.sleep()

确认问题是延迟

我建议使用商业分析工具。我喜欢[JProfiler](http://www.ej-technologies.com/products/jprofiler/overview.html,提供15天试用版)但StackOverflow社区也推荐YourKit。在本讨论中,我将使用JProfiler术语。

在Tomcat进程正常运行时附加到Tomcat进程,并了解它在正常情况下的外观。特别是,使用高级JDBC,JPA,JNDI,JMS,servlet,套接字和文件探测器来查看JDBC,JMS等操作需要多长时间(screencast。当服务器出现问题时再次运行它希望你会看到精确放慢的速度。在下面的产品截图中,你可以看到使用JPA Probe的SQL时序:

JPA hotspots http://static-aws.ej-technologies.com/SanJPN2pU9HB3g30N03BZsAwd77YzUtpXAsZoe9VUCi.png

然而,探针可能无法隔离问题 - 例如,它可能是一些线程问题。转到应用程序的“线程”视图;这将显示每个线程状态的运行图表,以及它是否在CPU上执行Object.wait(),正在等待进入synchronized块或正在等待网络I / O.当您知道哪个或哪些线程出现问题时,请转到CPU视图,选择线程并使用线程状态选择器立即深入查看昂贵的方法及其调用堆栈。 [Screencast]((screencast)。您将能够深入了解您的应用程序代码。

这是一个可运行时间的调用堆栈:

enter image description here

这是同一个,但显示网络延迟:

enter image description here

如果您知道阻止了什么,希望解决问题的途径会更加明确。

答案 2 :(得分:2)

我们遇到了同样的问题,运行在Java 1.7.0_u101(Oracle支持的版本之一,因为最新的公共JDK / JRE 7是1.7.0_u79),在G1垃圾收集器上运行。我无法判断问题是出现在其他Java 7版本中还是出现在其他GC中。

我们的流程是Tomcat运行Liferay Portal(我相信Liferay的确切版本在这里没有兴趣)。

这是我们观察到的行为:使用5GB的-Xmx,启动后的初始代码缓存池大小约为40MB。一段时间后,它下降到大约30MB(这是正常的,因为在启动期间有很多代码运行,永远不会再次执行,因此预计会在一段时间后从缓存中逐出)。我们观察到有一些JIT活动,所以JIT实际上填充了缓存(与我稍后提到的大小相比,似乎相对于整个堆大小的小缓存大小对JIT提出了严格的要求,这使得后者相当紧张地驱逐缓存)。然而,过了一段时间,没有更多的编译发生,JVM变得非常缓慢。我们不得不时不时地杀死我们的Tomcats以获得足够的性能,并且随着我们向门户添加更多代码,问题变得越来越糟(因为代码缓存更快地达到饱和,我猜)。

似乎JDK 7 JVM中有几个错误导致它不能重启JIT(看看这篇博文:https://blogs.oracle.com/poonam/entry/why_do_i_get_message),即使在JDK 7中,也是在紧急刷新之后(博客提到Java)错误8006952,8012547,8020151和8029091)。

这就是为什么手动将代码缓存增加到不太可能发生紧急刷新的水平“解决”问题的原因(我猜这是JDK 7的情况)。

在我们的示例中,我们选择升级到Java 8,而不是尝试调整代码缓存池大小。这似乎解决了这个问题。此外,代码缓存现在似乎相当大(启动大小约200MB,巡航大小达到约160MB)。正如预期的那样,在一些空闲时间之后,缓存池大小会下降,如果某个用户(或机器人或其他)浏览我们的站点,则会再次启动,从而导致执行更多代码。

我希望您发现上述数据有用。

忘了说:我发现博文,支持数据,推理逻辑和这篇文章的结论非常非常有帮助。谢谢,真的!

答案 3 :(得分:1)

  

有人知道在哪里看吗?

  1. 问题可能不在Tomcat / JVM中 - 您是否有一些批处理作业可以启动并强调共享资源(如公共数据库)?

  2. 进行线程转储,看看当应用程序响应时间爆炸时java进程正在做什么?

  3. 如果您使用的是Linux,请使用 strace 等工具,并检查java进程正在做什么。

答案 4 :(得分:1)

您是否检查过JVM GC时间?某些GC算法可能会“暂停”应用程序线程并增加响应时间。

您可以使用jstat实用程序来监控垃圾回收统计信息:

jstat -gcutil <pid of tomcat> 1000 100

以上命令每1秒打印一次GC统计数据100次。查看FGC / YGC列,如果数字不断增加,则GC选项有问题。

如果您希望将响应时间保持在较低水平,则可能需要切换到CMS GC:

-XX:+UseConcMarkSweepGC

您可以查看更多GC选项here

答案 5 :(得分:1)

在您的应用程序执行缓慢一段时间后会发生什么情况,它是否会恢复正常运行? 如果是,那么我会检查是否有任何与您的应用程序无关的活动。 类似于防病毒扫描或系统/数据库备份。

如果没有,那么我建议使用分析器(JProfiler,yourkit等)运行它,这些工具可以非常容易地指向您的热点。

答案 6 :(得分:0)

您正在使用Quartz,它管理定时进程,这似乎发生在特定时间。

发布您的Quartz计划并告知我们是否一致,如果是,您可以确定哪些内部申请流程可能会开始消耗您的资源。

或者,可能最终激活了部分应用程序代码,并决定将数据加载到内存缓存中。你正在使用Hibernate;检查对数据库的调用,看看是否有任何重合。