Tomcat性能优化及JVM内存工做原理

Java性能优化原则:代码运算性能、内存回收、应用配置(影响Java程序主要缘由是垃圾回收,下面会重点介绍这方面)javascript

代码层优化:避免过多循环嵌套、调用和复杂逻辑。css

 

Tomcat调优主要内容以下:html

1、增长最大链接数java

2、调整工做模式redis

3、启用gzip压缩算法

4、调整JVM内存大小数据库

5、做为Web服务器时,与Apache整合或Nginxapache

6、合理选择垃圾回收算法后端

7、尽可能使用较新JDK版本浏览器

 

生产配置实例:

<Connectorport="8080"protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="1000" minSpareThreads="100" maxSpareThreads="200" acceptCount="900" disableUploadTimeout="true" connectionTimeout="20000" URIEncoding="UTF-8" enableLookups="false" redirectPort="8443" compression="on" compressionMinSize="1024" compressableMimeType="text/html,text/xml,text/css,text/javascript"/>
 

参数说明:

org.apache.coyote.http11.Http11NioProtocol:调整工做模式为Nio

 

maxThreads:最大线程数,默认150。增大值避免队列请求过多,致使响应缓慢。

minSpareThreads:最小空闲线程数。

maxSpareThreads:最大空闲线程数,若是超过这个值,会关闭无用的线程。

acceptCount:当处理请求超过此值时,将后来请求放到队列中等待。

disableUploadTimeout:禁用上传超时时间

connectionTimeout:链接超时,单位毫秒,0表明不限制

URIEncoding:URI地址编码使用UTF-8

enableLookups:关闭dns解析,提升响应时间

compression:启用压缩功能

compressionMinSize:最小压缩大小,单位Byte

compressableMimeType:压缩的文件类型

 

Tomcat有三种工做模式:Bio、Nio和Apr,下面简单了解下他们工做原理:

Bio(Blocking I/O):默认工做模式,阻塞式I/O操做,没有任何优化技术处理,性能比较低。

Nio(New I/O or Non-Blocking):非阻塞式I/O操做,有Bio有更好的并发处理性能。

Apr(Apache Portable Runtime,Apache可移植运行库):首选工做模式,主要为上层的应用程序提供一个能够跨越多操做系统平台使用的底层支持接口库。

tomcat利用基于Apr库tomcat-native来实现操做系统级别控制,提供一种优化技术和非阻塞式I/O操做,大大提升并发处理能力。可是须要安装apr和tomcat-native库。

工做模式原理涉及到了网络I/O模型知识:

 

阻塞式I/O模型:应用进程调用recv函数系统调用时,若是等待要操做的数据没有发送到内核缓冲区,应用进程将阻塞,不能接收其余请求。反之,内核recv端缓冲区有数据,内核会把数据复制到用户空间解除阻塞,继续处理下一个请求。(内核空间(缓冲区)--用户空间(系统调用))

非阻塞式I/O模型:应用进程设置成非阻塞模式,若是要操做的数据没有发送到内核缓冲区,recv系统调用返回一个错误,应用进程利用轮询方式不断检查此操做是否就绪,若是缓冲区中有数据则返回,I/O操做同时不会阻塞应用进程,期间会继续处理新请求。

I/O复用模型:阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用上。能同时处理多个操做,并检查操做是否就绪,select/epoll函数发现有数据就绪后,就经过实际的I/O操做将数据复制到应用进程的缓冲区中。

异步I/O模型:应用进程通知内核开始一个异步I/O操做,并让内核在整个操做(包括数据复制缓冲区)完成后通知应用进程,期间会继续处理新请求。

I/O操做分为两个阶段:第一个阶段等待数据可用,第二个阶段将数据从内核复制到用户空间。

前三种模型的区别:第一阶段阻塞式I/O阻塞在I/O操做上,非阻塞式I/O轮询,I/O复用阻塞在select/poll或epoll上。第二阶段都是同样的。而异步I/O的两个阶段都不会阻塞进程。

wKiom1cPZiexhqQVAADEfIyyX4s521.png

Java性能问题主要来自于JVM,JVM GC也比较复杂,再调优以前了解下相关基础概念是必要的:

1)JVM内存划分分为年轻代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation)。

2)年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例大概是8:2。

3)堆内存(Heap)=年轻代+老年代。非堆内存=永久代。

4)堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,而后根据GC算法回收。

5)非堆内存用途:JVM自己使用,存放一些类、方法、常量、属性等。

6)年轻代:新生成的对象首先放到年轻代的Eden区中,当Eden满时,通过GC后,还存活的对象被复制到Survivor区的FromSpace中,若是Survivor区满时,会再被复制到Survivor区的ToSpace区。若是还有存活对象,会再被复制到老年代。

7)老年代:在年轻代中通过GC后还存活的对象会被复制到老年代中。当老年代空间不足时,JVM会对老年代进行彻底的垃圾回收(Full GC)。若是GC后,仍是没法存放从Survivor区复制过来的对象,就会出现OOM(Out of Memory)。

8)永久代:也称为方法区,存放静态类型数据,好比类、方法、属性等。

垃圾回收(GC,Garbage Collection)算法:

1)标记-清除(Mark-Sweep)

GC分为两个阶段,标记和清除。首先标记全部可回收的对象,在标记完成后统一回收全部被标记的对象。同时会产生不连续的内存碎片。碎片过多会致使之后程序运行时须要分配较大对象时,没法找到足够的连续内存,而不得已再次触发GC。

2)复制(Copy)

将内存按容量划分为两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另外一块上,而后再把已使用的内存空间一次清理掉。这样使得每次都是对半个内存区回收,也不用考虑内存碎片问题,简单高效。缺点须要两倍的内存空间。

3)标记-整理(Mark-Compact)

也分为两个阶段,首先标记可回收的对象,再将存活的对象都向一端移动,而后清理掉边界之外的内存。此方法避免标记-清除算法的碎片问题,同时也避免了复制算法的空间问题。

通常年轻代中执行GC后,会有少许的对象存活,就会选用复制算法,只要付出少许的存活对象复制成本就能够完成收集。而老年代中由于对象存活率高,没有额外过多内存空间分配,就须要使用标记-清理或者标记-整理算法来进行回收。

垃圾收集器:

1)串行收集器(Serial)

比较老的收集器,单线程。收集时,必须暂停应用的工做线程,直到收集结束。

2)并行收集器(Parallel)

多条垃圾收集线程并行工做,在多核CPU下效率更高,应用线程仍然处于等待状态。

3)CMS收集器(Concurrent Mark Sweep)

CMS收集器是缩短暂停应用时间为目标而设计的,是基于标记-清除算法实现,整个过程分为4个步骤,包括:

  • 初始标记(Initial Mark)

  • 并发标记(Concurrent Mark)

  • 从新标记(Remark)

  • 并发清除(Concurrent Sweep)

其中,初始标记、从新标记这两个步骤仍然须要暂停应用线程。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段是标记可回收对象,而从新标记阶段则是为了修正并发标记期间因用户程序继续运做致使标记产生变更的那一部分对象的标记记录,这个阶段暂停时间比初始标记阶段稍长一点,但远比并发标记时间段。

因为整个过程当中消耗最长的并发标记和并发清除过程收集器线程均可以与用户线程一块儿工做,因此,CMS收集器内存回收与用户一块儿并发执行的,大大减小了暂停时间。

4)G1收集器(Garbage First)

G1收集器将堆内存划分多个大小相等的独立区域(Region),而且能预测暂停时间,能预测缘由它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所得到空间大小以及回收所需时间),在后台维护一个优先列表,每次根据容许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内得到更高的收集效率。

G1收集器工做工程分为4个步骤,包括:

  • 初始标记(Initial Mark)

  • 并发标记(Concurrent Mark)

  • 最终标记(Final Mark)

  • 筛选回收(Live Data Counting and Evacuation)

初始标记与CMS同样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也能够与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运做而致使标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所指望的GC暂停时间来执行回收。

 

了解了JVM基础知识,下面配置下相关Java参数,将下面一段放到catalina.sh里面:

 

JAVA_OPTS="-server -Xms1024m -Xmx1536m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:+UseParallelGCThreads=8 XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:-PrintGC -XX:-PrintGCDetails -XX:-PrintGCTimeStamps -Xloggc:../logs/gc.log"

参数

描述

-Xms 堆内存初始大小,单位m、g
-Xmx 堆内存最大容许大小,通常不要大于物理内存的80%
-XX:PermSize 非堆内存初始大小,通常应用设置初始化200m,最大1024m就够了
-XX:MaxPermSize 非堆内存最大容许大小
-XX:+UseParallelGCThreads=8 并行收集器线程数,同时有多少个线程进行垃圾回收,通常与CPU数量相等
-XX:+UseParallelOldGC 指定老年代为并行收集
-XX:+UseConcMarkSweepGC CMS收集器(并发收集器)
-XX:+UseCMSCompactAtFullCollection 开启内存空间压缩和整理,防止过多内存碎片
-XX:CMSFullGCsBeforeCompaction=0 表示多少次Full GC后开始压缩和整理,0表示每次Full GC后当即执行压缩和整理
-XX:CMSInitiatingOccupancyFraction=80% 表示老年代内存空间使用80%时开始执行CMS收集,防止过多的Full GC

注意:不是JVM内存设置越大越好,具体仍是根据项目对象实际占用内存大小而定,能够经过Java自带的分析工具来查看。若是设置过大,会增长回收时间,从而增长暂停应用时间。

 

博客地址:http://lizhenliang.blog.51cto.com

QQ群:323779636(Shell/Python运维开发群)

 

gzip压缩做用:节省服务器流量和提升网站访问速度。客户端请求服务器资源后,服务器将资源文件压缩,再返回给客户端,由客户端的浏览器负责解压缩并浏览。

 

使用Apache与Tomcat整合,由于Tomcat处理静态文件能力远不足Apache,所以让Apache来处理静态文件,Tomcat处理动态jsp文件,能够有效提升处理速度。

在集群架构下,会涉及到一个问题,怎么保存Session?

TomcatSessionID持久化三种方法:

   Session粘性:经过浏览器Cookie绑定SessionID,经过sticky模式将同一Session请求分配到同一Tomcat上。

   Session复制:Tomcat经过广播形式将Session同步到其余Tomcat节点,而且Linux下要手动开启开放广播地址。不易后端节点过多

 Session保存数据库(memcache、redis):将SessionID保存在共享的数据库中。

 

 

OOM(Out of Memory)异经常见有如下几个缘由:

1)老年代内存不足:java.lang.OutOfMemoryError:Javaheapspace

2)永久代内存不足:java.lang.OutOfMemoryError:PermGenspace

3)代码bug,占用内存没法及时回收。

 

前两种状况经过加大内存容量,能够获得解决。若是是代码bug,就要经过jstack、jmap、jstat自带的工具分析问题,定位到相关代码,让开发解决。

相关文章
相关标签/搜索