解决JAVA服务器性能问题(下)

发表于:2009-8-24 16:26

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Ivan Small;xMatrix    来源:51Testing博客

  监视

  在生成合理的用户负载后,监视工具需要收集进程的运行状况。在我的测试环境中可以收集到各种有用的信息:

  1、所有计算机、网络设备等等的使用率

  2、JVM的统计数据。

  3、个别JAVA方法所花费的时间。

  4、数据库性能信息,包括SQL查询的统计。

  5、其他应用相关的信息。

  当然这些监视也会影响负载测试,但如果影响比较小也可以忽略。基本上如果我们想获取所有上面的信息,肯定会影响测试的性能。但如果不是一次获取所有信息还是有可能保证负载测试的有效性。仅对特定的方法设置定时器,仅获取低负载的硬件信息和低频率地获取样例数据。当然不加载监视器来做测试是最好的,然后和加载监视器的测试来做比较。虽然有时候侵入式监视是个好主意,但就不可能有监视结果了。

  获取所有监视数据到一个中央控制器来做分析是最好的,但使用动态运行时工具也可以提供有用的信息。例如,命令行工具如PS、TOP、VMSTAT可以提供UNIX机器的信息;性能监视器工具可以提供WINDOWS机器的信息;而TeamQuest, BMC Patrol, SGI's Performance Co-Pilot, and ISM's PerfMan这样的工具会在所有的测试环境中的机器安装代理并且将需要的信息传回中央控制机,这样就可以提供文本或可视化的信息。在本文中,我使用开源的Performance Co-Pilot作为测试统计的工具。我发现他对测试环境的影响最小,并且以相对直接的方式来提供数据。

  JAVA分析器提供很多信息,但通常对负载测试来说影响太大而没有太多的用处。工具甚至可以让你在负载服务器上做一些分析,但这也很容易便测试无效。在这些测试中,我激活了详细的垃圾收集器来收集内存信息。我也使用jconsole 和jstack工具(包含在J2SE 1.5中)来检查高负载下的VM。我没有保留这些测试用例中负载测试的结果因为我认为这些数据不是很正确。

  同步瓶颈

  在诊断服务器问题时线程的信息是非常有用的,特别是对同步之类的问题。jstack工具可以连接到运行的进程并且保存每一个线程的堆栈信息。在UNIX系统可以用信号量3来保存线程的堆栈信息,在WINDOWS系统的控制台中可以用Ctrl-Break。在第一项测试中,jstack指出许多线程在grindCPU()方法中被阻塞。

  你可以已经注意到列表2中grindCPU()方法的同步修饰符实际上并不必须。我在后一项测试中删除了他,如图2显示

Figure 2

  在图2中,你会注意到性能下降了。虽然我使用了更多的CPU,但吞吐量和失败数都更差了。虽然垃圾回收周期变了,但每秒依然需要回收100M。显然我们还没有找到主要的瓶颈。

  非竟争的同步相对于简单的函数调用还是很费时的。竟争性的同步就更费时了,因为除了内存需要同步外,VM还需要维护等待的线程。在这种状况下,这些代价实际上要小于内存瓶颈。实际上,通过消除了同步瓶颈,VM内存系统承担了更多的压力最后导致更差的吞吐量,即使我使用了更多的CPU。显然最好的方式是从最大的瓶颈开始,但有时这也不是很容易确定的。当然,确保VM的内存处理足够正常也是一个好的开始方向。

  内存瓶颈

  现在我会首先也定位内存问题。列表3是GrinderServlet的重构版本,使用了StringBuffer实例。图3显示了测试结果。

  Listing 3

  package pub.capart;
  /**
  * This is a simple class designed to simulate an application consuming
  * CPU, memory, and contending for a synchronization lock.
  */
  public class Grinderv2 {
  private static Grinderv2 singleton = new Grinderv2();
  private static final String randstr =
  “this is just a random string that I'm going to add up many many times”;
  private StringBuffer sbuf = new StringBuffer();
  private StringBuffer sbufrev = new StringBuffer();
  public static Grinderv2 getGrinder() {
  return singleton;
  }
  public synchronized void grindCPU(int level) {
  sbufrev.setLength(0);
  sbufrev.append(randstr);
  sbuf.setLength(0);
  for (int i=0;i<level;++i) {
  sbuf.append(sbufrev);
  reverse();
  }
  return sbuf.toString();
  }
  public String getReverse(String s) {
  StringBuffer sb = new StringBuffer(s);
  sb = sb.reverse();
  return sb.toString();
  }
  }

Figure 3

  通常重用StringBuffer并不是一个好主意,但这里我只是为了重现一些常见的问题,而不量提供解决方案。内存数据已经从图上消失了因为测试中没有垃圾回收器运行。吞吐量戏剧性的增加而CPU使用率又回到了50%。列表3不只是优化了内存,但我认为主要了改善了过度的内存消耗。

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号