【译】Java服务器调优

随着成千上万的Java服务器运行在企业线上环境,Java已经成为构建线上系统的语言之一。如果希望我们的机器表现出可接受的性能,那么就需要对它们进行定期调优。这篇文章详细阐述了Java服务器调优的各项技术。

衡量性能

为了让我们的调优有意义,我们需要某种方法来衡量性能是否提高。让我们记住两个重要的性能指标:延迟和吞吐量。

  • 延迟(Latency) 衡量的是端到端的某个操作的处理时间。在分布式环境中我们通常用发送请求和接收到响应整个来回的时间来衡量延迟。在那些场景,延迟是从客户端机器开始衡量的,并且也包括网络传输开销
  • 吞吐量(Throughput)衡量的是服务器在某段时间间隔(比如一秒)内处理的消息数。吞吐量可以用下面的公式来计算:

    吞吐量 = 请求数量 / 完成这些请求花的时间

理想情况下我们想要获得最大的吞吐量同时获得最小的延迟。然而鱼和熊掌不可兼得,一个设计良好的服务器系统中必然存在这二者的折衷(tradeoff)。例如下图所示,如果你想要获得更大的吞吐量,那么你必须增大并发度,但是相应地会导致平均延迟增加。通常,你必须实现最大吞吐量的同时保持延迟在一个可接受范围内。例如,你可能会选择某个范围的吞吐量,并且保证延迟低于10ms。

下图捕捉了服务器的行为。正如图中所示,服务器性能是通过衡量延迟和吞吐量与并发度的关系来实现的。

图中,“ideal path”是理想中的情况。实际上,大多数服务器在并发度过高情况下将崩溃或者出现性能下降。

服务器调优

在调优环节,我们试着深入服务器内部理解它的行为,并且要么证明服务器按预期在运行,要么寻找途径提高服务器性能。性能调优的目标有三个:

  1. 增大吞吐量 - 最大化系统单位时间内能够处理的消息数
  2. 减小延迟 - 确保我们满足响应时间SLAs(Service Level Agreement)
  3. 发现并修复泄露(比如内存、文件、线程、连接泄露)

正如之前所说,我们的目标是获得最大吞吐量同时保持延迟在可接受范围内。如果你还没有进行过任何调优,最好从调优吞吐量开始。那么我们就先从吞吐量调优开始吧。

吞吐量调优

在我们开始前,我们先弄懂什么会限制性能。把服务器设想为水管系统是很有帮助的;增大并发度就好比增大往水管系统中灌入的水量。增加更多的水并不能保证管道中将有更多的水流动;水流由管道系统中最慢的部分决定。

类似地,应用性能由系统最稀缺的资源决定。计算机系统有很多种资源:CPU、内存、磁盘和网络IO。任何其中一种资源都可能限制我们系统的性能。

当尝试从外部衡量系统性能时,我们可以逐渐增大负载直到一种资源消耗殆尽。那将有助于发现限制性资源,然后我们要么分配更多的那种类型资源,要么修改系统以更节省地使用那种资源。

下一步,我们搭建起系统并且对系统施加相当量的工作负载(更多细节,可以参考我的博客如何衡量服务器性能)。当负载在运行时,下一步我们将找出各种类型资源的利用程度。

基于最稀缺资源维度,我们将服务器性能下降归结为以下三类:

  1. CPU紧缺型 - 服务器阻塞等待CPU
  2. IO紧缺型 - 服务器因为磁盘或者网络带宽而阻塞
  3. 延迟密集型 - 服务器等待某些事件发生(比如等待数据从磁盘传输到网络)

下面让我们来研究下某示例系统中那种资源最稀缺。我们先在Unix/Linux系统中运行top命令:

工程师常犯的一个错误是在还没有真正确定是否真正属于CPU紧缺型的场景时,就开始调优CPU。虽然上图显示的CPU利用率很低,当前机器可能正忙于做IO操作(比如读磁盘、写数据到网络)。Load Average是衡量机器是否满载的更好指标。

Load Average表示在OS调度队列中等待的进程数。不像CPU,Load Average将因为任何一种资源的紧缺而增大(比如CPU、网络、磁盘、内存...)。更多细节请参考理解Linux的Load Average

我们可以利用下面的Load Average值来确定机器是否处于高负载状态:

  • 如果Load Average < CPU核数,那么机器就非满载
  • 如果Load Average == CPU核数,那么机器资源就被充分利用了
  • 如果Load Average >= 4*CPU核数,那么机器就处于过载状态
  • 如果Load Average >= ~40*CPU核数,那么机器就处于不可用状态

如果机器非满载,一般表示它处于空闲状态。有好几个因素可能引起这种情况,可以通过下面的方法来校正:

  1. 试着增大负载(通常表示增大并发度)。例如,用一两个客户端测试服务器通常不会让服务器满载;通常需要成百上千个客户端同时测试服务器才可能让其满载
  2. 剖析锁状况(如果大部分线程都在等待锁或者发生死锁,那么吞吐量也会下降)。尽可能发现并修复那些你可以找到的情况,并尽可能使用非阻塞数据结构。我们将在“延迟调优”小节更详细讨论锁剖析
  3. 调整线程池大小(有时候系统配置过少的线程数,可能会引起系统运行缓慢)。例如我发现增加Tomcat线程池的大小通常会增大吞吐量
  4. 确保网络未饱和(如果网络处于饱和状态,那么能够到达机器的有效负载就可能非常小了)。在大多数机器中你可以用Linux提供的iftop命令来检查这种情况

另一方面如果机器满载,那么很明显它在做什么事情,但你仍然需要确保它在做一些有意义的事情!

  • 确保你的应用是运行在同一台机器上的唯一一个重量级进程。你应该不想看到其他应用使你的测试结果扭曲
  • 如果排除了上面这种情况,然后用top命令检查CPU利用率。如果CPU利用率很高,使用一个profiler工具来看CPU Profile信息

例如上图是从JProfiler截取过来的CPU Profile信息。它显示了服务器中Java方法执行树,标明了每个方法的执行时间。检查最耗CPU时间的方法并确保它们在做有意义的事情。(注意:这篇文章中我们使用JProfiler,也可以使用其他工具如Youkit Profiler和JDK 1.7+提供的Java Mission Control)

  • 计算应用花在GC上的时间。如果GC时间占比超过10%那么你就需要JVM GC调优了(你可以使用类似VisualVM GC插件这样的工具来完成,参考我的博客我是否该进行GC调优?)。如果GC存在问题,Profiler的allocation view可以帮助你发现内存分配热点并且修复它们。这里有个演讲是GC调优方面不错的参考资源:talk by Kirk pepperdine
  • 再下一步是检查网络和磁盘的IO状况(假设你的程序写磁盘)下面的截图显示了从JProfiler截取的IO Profile信息。验证高IO节点确实出现在预期位置。然后对数据库访问做同样操作

  • 最后检查下你的机器是否在进行内存分页(比如 Check Swap Usage in Linux)。一般来说,你需要避免内存切换因为那将大幅度拖慢服务器性能。如果存在内存切换,你要么修改服务器以需要更少的内存,要么为机器增加物理内存

如果你把上面所有步骤都尝试遍了仍然没有达到预期性能,很可能是服务器达到了它自身的性能瓶颈。你要么通过加服务器来Scale Up,要么重新设计服务器架构。

延迟调优

高延迟是由费时请求处理操作引起的。磁盘访问、网络访问和锁是引起处理操作时间长的几大罪魁祸首。

当进行延迟调优时,我们首先要检查网络和磁盘状况,正如我们上一节讨论的,并且发现并减少IO操作数量。下面是一些可能有用的校正方法:

  1. 避免不必要的IO操作。尽可能消灭它们或者用Cache替代
  2. 尝试批量IO,批量IO将比多个单次IO节省开销
  3. 如果你能提前猜到所需要的数据,那么可以尝试预加载(或者预取)数据

现在让我们来看一个JVM线程视图。下图显示了线程的数目和状态随时间变化情况。红色区域表示很多线程阻塞在锁上。

如果从线程视图发现许多线程处于等待状态,那么可以在展开“Monitor and Locks”视图找出引起阻塞的线程:

上面的截图显示了哪一段代码阻塞了最长时间和那一段代码在这段时间内持有锁。以下是两条总原则:

  1. 尽量避免synchronzied语句块和锁。你通常可以使用性能表现更良好的java.util.concurrent包中的并发数据结构
  2. 当你不得不使用锁或者写synchronized语句块时,尽可能早的释放锁。当持有锁时,尽量减少耗时操作,比如IO。此外在Lock或者synchronized语句块中尽量避免再获取其他锁

再下一步检查客户端与服务器之间的网络行为。你可以通过类似ping和iftop之类的命令来操作,但你最好咨询网络管理员获取详细的网络行为信息。

最后一个可选项是引入更多的服务器从而减少每个服务器的并发度进而减小延迟。一个极端的例子是运行两份服务器实例拷贝然后使用Jeff Dean在他的演讲Taming Latency Variability中提到的第一份结果。此外,如果你想要获得非常低的延迟,考虑使用类似 LMX disruptor这样的工具。

性能调优清单

在文中我们讨论了吞吐量调优和延迟调优。或许你现在会感叹:Java服务器调优是一项技巧活,文中所提到的也只是冰山一角。下面是性能调优步骤总清单。它或许不会告诉你有关调优的一切,但它可能帮助你避开很多陷阱:

  1. 检查机器的Load Average。如果它大于4*CPU核数,那么机器处于过载状态,你可以直接跳到CPU调优部分。
  2. 你是否给系统施加了足够的负载?通过增加线程数来模拟更多的客户端操作。如果那样做提高了吞吐量那么继续增大负载知道达到最大吞吐量。
  3. 线程是否处于空闲状态?如果你有太多的锁或者太少线程,系统可能提供不了足够的吞吐量。使用一个Profiler工具查看锁状态并且尝试移除锁。尽管一些锁难以避免,但大部分情况是没必要的。
  4. 尝试增加线程池中的线程数,检查那样做是否能增加吞吐量。
  5. 现在检查CPU热点代码
  6. 查看CPU热点代码的树视图并确保热点代码出现在预期位置。例如一个XML解析器预期会消耗很多CPU。但如果你发现非预期行为消耗了过多CPU,那么就需要修复它了。
  7. 检查内存和GC,如果GC吞吐量小于90%,那么就需要进行GC调优。如果内存不断切换分页,那么增大内存并观察是否有帮助。
  8. 观察DB Profiles,确保最高负载部分出现在预期位置。
  9. 观察网络IO Profiles。找到热点,确保大多数写都在你的预期之中。
  10. 确保底层资源如网络和Disk Mounting就绪

Java服务器调优非常具有技巧性,但相应的回报也很丰厚。有时候它更像是艺术而非工程,但根据我提供的步骤应该能让你走得更远。

翻译自:Tuning Java Servers

转载请注明出处:http://my.oschina.net/feichexia/blog/348773 谢谢。

时间: 2024-08-25 08:23:27

【译】Java服务器调优的相关文章

《java系统性能调优》--1.发现瓶颈

性能啊!性能! 之所以想写写性能调优,也是有感于我们的项目,我们采用一些手段使得系统性能上升了一个台阶,总是需要把这点经验沉淀一下.随着工作的深入,关于系统性能的事肯定还有很多,也算是通过这个系列文章做做笔记.优化可能包括应用级别的优化,也可能包括代码级别的优化. "要进行优化,先得找到性能瓶颈!" 忘记是从哪里看到了这句话,但总算切中要害. 但在找性能瓶颈之前,我们总要先对系统性能有一个概念. 如何在不购买新硬件的条件下完成更多的工作?何时才真正需要添加硬件(更多的内存,更快的磁盘.

成为Java GC专家(5)—Java性能调优原则

这是"成为Java GC专家"系列的第五篇文章.在第一篇深入浅出Java垃圾回收机制中,我们已经学习了不同的GC算法流程.GC的工作原理.新生代(Young Generation)和老年代(Old Generation)的概念.你应该了解了JDK7中5种GC类型以及各种类型对应用程序的影响. 在第二篇如何监控Java的垃圾回收中,阐述了JVM是怎样实际执行垃圾回收的,我们怎样去监控GC以及哪些工具能让这个过程更高效. 第三篇如何如何优化Java垃圾回收机制中展示了一些基于真实案例的最佳

《java系统性能调优》--2.缓存

上一节,简单介绍了如何发现性能瓶颈.从这节开始,我会和大家分享我在项目中做的一些性能调优工作.这个系列没有什么顺序可言,觉得什么重要,就说说什么. 这节,我们聊缓存. 最开始接触缓存这个词,是学习硬件知识的时候,cpu有缓存,而且还分一级缓存,二级缓存,三级缓存.. 记得曾经的曾经老师提了一个很有意思的问题. 问:电脑为什么要有一级缓存,二级缓存--,而且还要有内存,还要有硬盘? 如果你面对这个问题,你怎么回答? 先来看我们的正文,最后再解释. 我们要聊当然不是硬件意义上的缓存,而是应用程序与应

Java性能调优笔记

Java性能调优笔记 调优步骤:衡量系统现状.设定调优目标.寻找性能瓶颈.性能调优.衡量是否到达目标(如果未到达目标,需重新寻找性能瓶颈).性能调优结束. 寻找性能瓶颈 性能瓶颈的表象:资源消耗过多.外部处理系统的性能不足.资源消耗不多但程序的响应速度却仍达不到要求. 资源消耗:CPU.文件IO.网络IO.内存. 外部处理系统的性能不足:所调用的其他系统提供的功能或数据库操作的响应速度不够. 资源消耗不多但程序的响应速度却仍达不到要求:程序代码运行效率不够高.未充分使用资源.程序结构不合理. C

java 性能调优工具

1.jstack 用法jstack [option] pid -l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况 -m mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法) 找出进程内最耗费CPU的线程,可以使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid printf "%x\n" pid 得到pid的

服务器调优

linux 服务器调优 Linux内核参数 net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies 当出现SYN等待队列溢出时,启用cookies来处理,减少SYN攻击,默认0表示关闭, net.ipv4.tcp_tw_reuse = 1 表示开启重用.允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭: net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为

java 性能调优和GC

JAVA 性能调优和GC http://blog.csdn.net/gzh0222/article/details/7663181 JAVA GC调优手记 http://blog.csdn.net/firecoder/article/details/7225654 JAVA GC 日志详解 http://blog.csdn.net/alivetime/article/details/6895537 JAVA 内存参数设置 http://heipark.iteye.com/blog/1356113

JAVA性能调优-在循环条件中不要使用表达式

1.JAVA性能调优-在循环条件中不要使用表达式 我们在学习JAVA性能调优的时候,经常能看到这一的一段话:在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. import java.util.vector; class cel { void method(vector vector) { for (int i= 0; i < vector.size (); i++)  //violation ; //... } } 更正:

Java性能调优_深入Java程序性能调优(并行开发、JVM调优)

深入Java程序性能调优(阿姆达尔定律.缓存组件.并行开发.线程池.JVM调优)课程讲师:special课程分类:Java核心适合人群:初级课时数量:33课时更新程度:完成用到技术:阿姆达尔定律.缓存组件.并行开发.线程池.JVM调优涉及项目:模式在实际开发中运用深入Java程序性能调优下载: http://pan.baidu.com/s/1ntn0ZTB 密码: ijluJava性能调优:国内关于Java性能调优的课程非常少,如此全面深入介绍Java性能调优,北风算是独家,Special讲师,