本文是GC专家系列中的第四篇。在第一篇理解Java垃圾回收中咱们学习了几种不一样的GC算法的处理过程,GC的工做方式,新生代与老年代的区别。因此,你应该已经了解了JDK 7中的5种GC类型,以及每种GC对性能的影响。apache
在第二篇Java垃圾回收的监控中介绍了在真实场景中JVM是如何运行GC,如何监控GC数据以及有哪些工具可用来方便进行GC监控。segmentfault
在第三篇GC 调优中基于真实案例介绍了可用于GC调优的最佳选项。同时也描述了如何经过下降移动到老年代中对象的数量来缩短Full GC耗时,以及如何设置GC类型及内存大小。tomcat
本文将介绍Apache的MaxClients
参数的重要性以及在GC发生时对系统总体性能的显著影响。经过几个例子,你将会更清晰的理解MaxClients
值所引起的问题。最后会介绍如何依据系统的可用内存来为MaxClients
设置合理的数值。服务器
NHN的服务运行环境中有大量的流控(Throttle valve)选项,这些选项对系统的稳定运行具备重要做用。咱们来看下Apache的MaxClients
选项在Tomcat发生Full GC时会对系统带来哪些影响。socket
大部分的开发人员都知道GC 发生中会伴随着"stop the world(STW)现象"(具体详情参考理解Java垃圾回收)。尤为是NHN的Java开发人员可能都经历过在Tomcat中由GC相关问题而致使的系统崩溃。由于JVM管理内存,所以Java应用系统不可避免的会遇到GC引发的STW现象。工具
在你开发的线上系统中,GC天天都会发生不少次。在GC发生时,即使TTS没有发生,却依然可能会给用户503的错误响应。性能
根据结构特色,Web服务更适合于作横向扩展而非单纯的提升单一机器的性能。因此一般根据性能须要,Web服务的服务器部署结构由一台Apache服务器和多台Tomcat服务器组成。在本文中,假设一个Apache服务和Tomcat服务部署在同一台物理主机上,以下图所示:学习
图1: 本文假设的服务运行环境spa
做为参考,本文所述参数均是基于Apache 2.2.21(prefork MPM),Tomcat 6.0.35,jdk 1.6.0_24,并运行在CentOS 4.7.2(32位)操做系统上。
系统内存2GB,并使用ParallelOldGC垃圾回收,默认开启了AdaptiveSizePolicy
选项并设置堆大小为600MB。
假设Apache的流量为200QPS,并开启10个httpd处理进程(尽管实际场景依赖于请求的响应时间)。在这种前提下,假设full GC致使的停顿耗时1秒,若是Tomcat发生了Full GC将会怎么样?
首先你能想到的是full GC致使Tomcat停顿,处理中的请求将得不到响应。若是这样,Tomcat暂停,请求得不处处理,Apache将会怎么样?
即便Tomcat因Full GC而暂停处理,而请求却仍以200 req/s的速度到达Apache。在full GC发生前,只须要10个或者稍微多一点的httpd进程就能够快速响应服务请求。可是如今Tomcat暂停了,为了处理新的请求Apache将持续建立新的httpd进程直到httpd.conf文件中定义的MaxClients
阀值。由于MaxClients
默认值为256,因此200 req/s的请求并不会带来太大问题。
这个时候,新建立的httpd 进程会怎么样?
Httpd 进程使用mod_jk模块管理的AJP链接池中的空闲链接把请求发送到Tomcat。若是没有空闲链接,则会要求建立新的链接。然而由于Tomcat处理暂停状态,新建链接的请求将被拒绝。因此这些请求将会放到堆积队列(backlog queue),队列的长度是server.xml的AJP Connector中设置的。
若是请求数据超出了堆积队列的长度,Apache将会收到链接拒绝错误,并把这个错误以HTTP 503的方式返回给用户。
在本例的中,堆积队列的长度默认设置为100,而请求速度为200 req/s,所以在由full GC致使Tomcat暂停的这1秒中,将有超过100的请求将会收到503错误。
Full GC结束以后,堆积队列中的socket链接会被Tomcat接收并分配给工做线程(最大工做线程数由MaxThreads
决定,默认值为200)来处理请求。
在上面的场景中,如何设置才能避免给用户返回503错误?
首先咱们须要知道,应该设置足够的堆积队列长度以容纳在Tomcat Full GC致使的暂停期间流入的请求。所以堆积队列最小长度至少为200(上文中QPS为200)。
这样配置之后,是否还有其余问题?
把堆积队列长度设置为200后,咱们再次重复上面的场景。结果问题却比以前更加严重。
正常状况下系统内存使用量维持在50%,而在发生Full GC时内存使用却迅速上升到100%,引发内存交换区(swap)使用量的极剧增长。更为严重的是Full GC致使的响应停顿由原来的1秒增长到了4秒,直接后果就是期间系统像挂掉了同样,不能响应任何请求。
在以前的场景中,只有100左右的请求会收到 503 的错误,而增长堆积队列到200后却致使了500甚至更多的请求被挂起至少3秒不能收到任何响应。
这个例子很好的证实了若是不能准备的理清配置信息之间的因果关系,可能会对系统带来极为严重的影响。
为何会这样?
原理就是要清楚MaxClients
选项的特性。
MaxClients
的值不易设置过大,设置MaxClients
的关键在于即使建立了MaxClients
数量的httpd进程,也要须要维持应用系统的内存使用量不该超过80%。
系统交换区默认值为60,所以若是内存使用超过80%,系统将会发生频繁的内存交换。
咱们再来看下为何这个特性会致使上面所述的严重后果。
当请求的QPS为200时,Tomcat会被Full GC暂停响应,而后把堆积列队容量设置为200。起初大约有100个额外的httpd 进程会被Apache建立,紧接着内存使用量超过了80%,引发操做系统主动的使用交换区的内存空间,而因GC存活在JVM老年代中的对象被操做系统误认为长时间未使用,从而致使这些对象被移动到交换区。
最后,当GC过程当中涉及到交换区时,耗时就会迅速增长。然后httpd进程数继续增长,致使内存使用量达到了100%,从而出现了上述的严重后果。
上述案例的先后区别仅在于堆积队列的长度:100和200。但为何在200时会出现更严重的情况?
缘由是堆积队列不一样的长度致使了httpd进程数的不一样。当值为100时,在发生Full GC时100个请求所要求建立的链接被置于堆积队列中。再有新的请求会被拒绝并返回503错误,因此系统的整个httpd的进程数仅超出100不多的数量。
但当队列长度设置为200时,有200个请求被接收并置于队列中。从而致使httpd进程的数量超过200,并触发了操做系统进行内存交换的阀值。
因此,若是不顾内存使用状况而一味的加大MaxClients
的数值,将会致使Full GC时httpd进程数迅速增长,引进内存交换并最终下降系统的总体性能。
因此如何设置MaxClients,如何找到当前系统的阀值?
若是系统总内存为2GB,设置MaxClient
的值须要保证在任什么时候候内存的使用量不超过80%即1.6GB,从而避免因内存交换致使的性能降低。也就是说仅有1.6GB空间供Apache, Tomcat和其余默认安装的代理程序共享和分配内存。
假如默认安装的代理程序占用200M内存;Tomcat的堆空间设置-Xmx
为600M,以下图所示,Tomcat总占用量将725M (持久代 + 本地堆空间)。Apache可以使用的空间为剩下的700M。
图 2:Top命令的截图
对于Apache的700M内存,该如何设置合理的MaxClients值?
固然这也取决于Apache加载的模块类型和数量。以NHN的Web服务为例,把Apache看成简单的代理使用,根据上图RES显示,4M空间对于每一个httpd进程来讲已足够使用。所以700M空间能设置的MaxClients
为175。
可靠的服务配置要可以在满载的状况降低低系统停顿时间并可以最大范围的保证成功响应用户请求。对于Java应用来讲,必需要确认在Full GC引发的SWT状况下,系统的配置是否可以提供足够可靠的服务。
若是为了应对单纯的请求增长和防止DDos攻击,在不考虑内存使用的状况下把MaxClients
设置过大,那么MaxClients
不但会失去做为流控的用途,反而会带来更为严重的后果。
在这个案例中,解决问题的最优途径是加大系统的内存,或者设置MaxClients
为175(上面的计算结果)以保证只有QPS超过175时才会出现503错误。
做者:Dongsoon Choi,游戏服务技术支持团队高级工程师,NHN公司