【Java 调优之路】Tomcat 性能优化

2,522 阅读8分钟
原文链接: click.aliyun.com

Java性能优化原则:代码运算性能、内存回收、应用配置(影响Java程序主要原因是垃圾回收)

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


调优内容:

1、增加最大连接数

2、调整工作模式

3、启用gzip压缩

4、调整JVM内存大小

5、作为Web服务器时,与Apache整合或Nginx

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

有三种工作模式: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

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

minSpareThreads:最小空闲线程数。

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

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

disableUploadTimeout:禁用上传超时时间

connectionTimeout:连接超时,单位毫秒,0代表不限制

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

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

compression:启用压缩功能

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

compressableMimeType:压缩的文件类型

 

调整JVM内存大小:

在catalina.sh增加JAVA_OPTS='-Xms512m -Xmx1024m -XX:PermSize=128m-XX:MaxPermSize=256m'

-Xms JVM初始最小堆内存,默认为物理内存1/64,不要设置过大,否则增加回收时间(暂停应用),相对频率少,相反,频率高。

-Xmx JVM最大允许堆内存大小,默认为物理内存1/4

-XX:PermSize JVM初始分配非堆内存大小

-XX:MaxPermSize  JVM最大允许分配的非堆内存

涉及堆(Heap)和非堆(Non-Heap) 内存知识:

堆内存是存储对象,例如类实例、数组等,在JVM中堆之外的内存称为非堆内存。可以理解为非堆内存留给JVM 自己用。

堆中存的是对象,栈中存的是基本数据类型和堆中对象的引用。在栈中,一个对象对应一个4byte的引用。

栈是运行时的单位,而堆是存储的单位。

 

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


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


垃圾回收涉及知识:

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

1.        标记-清除算法(Mark-Sweep)

分为两个阶段,标记和清除。收集器从根节点开始标记所有被引用对象,并标记可用对象,然后对未标记对象执行清除。回收后的空间是不连续的。缺点是暂停整个应用,同时,会产生碎片。

2.        复制算法(copying)

将内存分为两块,每次只使用其中一块,垃圾回收时,将标记的对象拷贝到另外一块中,然后完全清除原来使用的那块内存。复制后的空间是连续的。缺点是需要两块内存空间。

3.        标记-整理算法(Mark-compact)

结合标记-清除和复制两个算法优点。也分为两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二个阶段遍历整个堆,清除未标记对象,并且把存活对象压缩到堆的其中一块,按顺序排放。此方法避免标记- 清除的碎片问题,同时也避免了复制算法的空间问题。

垃圾收集器技术:

1.        串行收集

使用单线程处理所有垃圾回收工作,实现容易,效率高,但无法使用多处理器优势,所以这种适合单处理器服务器、数据量(100M左右)比较小和响应时间无要求的场景。

2.        并行收集

使用多线程处理垃圾回收工作,速度快,效率高。理论上处理器越多,性能越好。适合数据量大,响应时间无要求场景。

3.        并发收集(CMS

前面两个在垃圾回收工作时,需要暂停整个应用,暂停时间跟堆大小决定。先使用多线程来扫描堆内存,标记需要回收的对象,再清除被标记的,某些情况下也会暂停应用。适合数据量大,多处理器,响应时间有高要求的场景。

4.        G1 GC

将多个内存分割成多个独立区域,然后并对他们进行垃圾回收。释放内存后,G1还可以压缩空闲的堆内存。


选择哪种垃圾收集器,需要根据应用场景、硬件资源以及吞吐量来决定。一般采用CMS。

指定垃圾回收技术(catalina.sh JAVA_OPS指定):

-XX:+UseSerialGC    串行垃圾回收器

-XX:+UseParallelGC 并行垃圾回收器

-XX:+UseConcMarkSweepGC  并发标记扫描垃圾回收器

-XX:ParallelCMSThreads=        并发标记扫描垃圾回收器 =为使用的线程数量

打印垃圾回收信息:

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename


使用Apache与Tomcat整合,因为Tomcat 处理静态文件能力远不足Apache,因此让Apache来处理静态文件,Tomcat处理动态jsp 文件,可以有效提高处理速度。同时也会涉及到一个问题,怎么保存Session?

TomcatSessionID持久化三种方法:

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

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

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

 

经常会出现内存溢出的报错信息,原因所在?

满足这两个条件抛出内存溢出:JVM 98%的时间都花费在内存回收和每次回收的内存小于2%

1、建立过多的对象,没有及时释放,导致堆内存不足(堆内存大小受物理内存、虚拟内存限制)。

2、代码设计不合理,占用内存无法及时回收。