Tomcat配置优化,主要在于优化tomcat运行模式,并发参数和线程数, 以及jvm堆内存和垃圾回收相关参数的优化.下面将逐一介绍.
1. tomcat的3种运行模式
1.1 BIO - 同步阻塞IO模式
BIO: 同步阻塞IO, 性能低, 没有经过任何优化处理和支持.
服务器实现模式为一个连接一个线程. 即, 客户端有连接请求时, 服务器端就需要启动一个线程进行处理. 如果这个连接不做任何事情会造成不必要的线程开销, 当然可以通过 线程池 机制改善.
适用场景: BIO方式适用于连接数比较小且固定的架构, 这种方式对服务器资源要求比较高, 有并发局限, JDK1.4之前的唯一选择.
1.2 NIO - 同步非阻塞IO模式
NIO 是 Java SE 1.4 及后续版本提供的一种新的IO操作方式(即java.nio包及其子包).
Java NIO是一个基于缓冲区、并能提供非阻塞IO操作的Java API, 因此NIO也被看成是non-blocking IO(非阻塞式IO)的缩写, 它拥有比传统BIO操作更好的并发性能.
服务器实现模式为一个请求一个线程, 即客户端发送的连接请求都会注册到多路复用器上, 多路复用器轮询到连接有IO请求时才启动一个线程进行处理.
适用场景: 适用于连接数较多且连接比较时间短(轻操作)的架构, 比如聊天服务器. 这种方式的并发性能局限于应用中, 编程比较复杂.
Tomcat 8.x默认运行在NIO模式下.
1.3 APR - 可移植运行时模式
APR(Apache Portable Runtime, Apache可移植运行时), 是Apache HTTP服务器的一个支持库, 它提供了一组映射到底层操作系统的API, 如果操作系统不支持特定功能, APR库将提供仿真. 因此开发人员可以使用APR使程序真正跨平台移植.
此模式的安装步骤比较繁琐, 但却从操作系统层面解决了异步IO的问题, 能大幅度提高应用性能.
APR的本质是使用 JNI 技术调用操作系统底层的IO接口, 所以需要提前安装必要的依赖, 具体配置方法见下文描述.
BIO | NIO | NIO2 | APR | |
---|---|---|---|---|
类名 | Http11Protocol | Http11NioProtocol | Http11Nio2Protocol | Http11AprProtocol |
引用版本 | ≥3.0 | ≥6.0 | ≥8.0 | ≥5.5 |
轮询支持 | 是 | 是 | 是 | |
轮询队列大小 | N/A | maxConnections | maxConnections | maxConnections |
读请求头 | 阻塞 | 非阻塞 | 非阻塞 | 阻塞 |
读请求体 | 阻塞 | 阻塞 | 阻塞 | 阻塞 |
写响应 | 阻塞 | 阻塞 | 阻塞 | 阻塞 |
等待新请求 | 阻塞 | 非阻塞 | 非阻塞 | 非阻塞 |
SSL支持 | Java SSL | Java SSL | Java SSL | Open SSL |
SSL握手 | 阻塞 | 非阻塞 | 非阻塞 | 阻塞 |
最大链接数 | maxConnections | maxConnections | maxConnections | maxConnections |
推荐使用nio,在tomcat8中有最新的nio2,速度更快,建议使用nio2
设置nio2:
<Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" />
2. server.xml相关配置
2.1 Tomcat并发配置(connector配置)
Tomcat的Connector是其接收HTTP请求的关键模块, 可以通过它来指定IO处理模式, 指定处理该Connector接收到的请求的线程数, 以及其他常用的HTTP策略.
配置路径: 在 ${TOMCAT_HOME}/conf/server.xml 文件的节点中进行配置.
2.1.1 使用线程池处理请求
使用线程池, 通过较少的线程资源来处理更多的请求, 从而提高Tomcat的请求处理能力.
前提: 要提前配置至少一个线程池来处理请求, 配置文件为${TOMCAT_HOME}/conf/server.xml.
其中Executor与Connector同级, 多个Connector可以使用同一个线程池来处理请求.
1) 参考默认连接池配置:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/>
2) 自定义线程池示例:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="200" minSpareThreads="10" maxIdleTime="600000" prestartminSpareThreads="true" maxQueueSize="100" />
3) 线程池参数说明:
name: 线程池名称.
namePrefix: 创建的每个线程的名称前缀, 单独的线程名称为 namePrefix + threadNumber.
maxThreads: 线程池中最大并发线程数, 默认值为200, 一般建议设置400~ 800 , 要根据服务器配置和业务需求而定.
minSpareThreads: 最小活跃线程数, 也就是核心线程数, 不会被销毁, 会一直存在.
prestartminSpareThreads: 是否在启动程序时就生成minSpareThreads个线程, 默认为false, 即不启动. 若不设置为true, 则minSpareThreads的设置就不起作用了.
maxIdleTime: 线程最大空闲时间, 超过该时间后, 空闲线程会被销毁, 默认值为6000, 单位为毫秒.
maxQueueSize: 最大的等待队列数, 超过则拒绝请求. 默认值为int类型的最大值(Integer.MAX_VALUE), 等同于无限大. 一般不作修改, 避免发生部分请求未能被处理的情况.
threadPriority: 线程池中线程的优先级, 默认值为5, 取值范围: 1 ~ 10.
className:线程池的实现类, 未指定情况下, 默认实现类为 org.apache.catalina.core.StandardThreadExecutor. 要自定义线程池就需要实现 org.apache.catalina.Executor 接口.
2.1.2 在Connector中使用线程池
Connector是Tomcat接收请求的入口, 每个Connector都有自己专属的监听端口.
<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
1) Connector的参数说明:
redirectPort="8443" # 基于SSL的端口, 在需要基于安全通道的场合, 比如当客户端的请求协议是HTTPS时, 将该请求转发到此端口.
minSpareThreads="25" # Tomcat连接器的最小空闲Socket线程数, 默认值为25. 如果当前没有空闲线程, 且没有超过maxThreads, 将一次性创建的空闲线程数量. Tomcat初始化时创建的线程数量也是此值.
maxSpareThreads="75" # 最大空闲线程数, 一旦创建的线程超过此值, Tomcat就会关闭不再需要的Socket线程, 默认值为50. 线程数可以大致用 "同时在线用户数、用户每秒操作次数、系统平均操作时间" 来计算.
keepAliveTimeout="6000" # 下次请求到来之前, Tomcat保持该连接6000ms.
maxKeepAliveRequests="10" # 该连接最大支持的请求数, 超过该请求数的连接也将被关闭(此时就会返回一个Connection: close头给客户端). 1表示禁用长连接, -1表示不限制连接个数, 默认为100, 一般设置在100~200之间.
acceptorThreadCount="1" # 用于接收连接的线程的数量, 默认值是1. 一般如果服务器是多核CPU时, 需要改配置为 2.
enableLookups="false" # 是否支持反查域名(即DNS解析), 默认为true. 为提高处理能力, 应设置为false.
disableUploadTimeout="true" # 上传时是否启用超时机制, 若为true, 则禁用上传超时.
connectionTimeout="20000" # 网络连接超时时间, 默认值为20000ms, 设置为0表示永不超时 —— 存在隐患. 通常可设置为30000ms.
URIEncoding="UTF-8" # 指定Tomcat容器的URL编码格式.
maxHttpHeaderSize="8192" # HTTP请求头信息的最大程度, 超过此长度的部分不予处理. 一般设置为8K即可.
maxPostSize="10485760" # 指定POST请求的内容大小, 单位为Byte, 默认大小为2097152(2MB), 10485760为10M. 如果要禁用限制, 可设置为-1.
compression="on" # 打开传输时压缩功能.
compressionMinSize="10240" # 启用压缩的输出内容大小, 默认为2048, 即2KB.
noCompressionUserAgents="gozilla, traviata" # 设置不启用压缩的浏览器
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" # 压缩的资源类型
2) 补充说明:
1>. Tomcat 的压缩是在客户端请求服务器对应资源后, 从服务器端将资源文件压缩, 再输出到客户端, 由客户端的浏览器负责解压缩并浏览. 相对于普通的浏览过程(如浏览HTML、CSS、Javascript和Text), 它可以节省40%左右的流量. 更为重要的是, 它也可以对动态生成的网页(包括CGI、PHP、JSP、ASP、Servlet、SHTML等)进行压缩. 2>. 需要注意的是, 压缩会增加Tomcat的负担, 最好采用 Nginx + Tomcat 或 Apache + Tomcat 方式, 将压缩交由 Nginx / Apache 去完成. 在server.xml的节点配置(尚未验证使用):
- <Service name="Catalina" /> --- 处理所有直接由Tomcat服务器接收的web客户请求.
- <Service name="Apache" /> --- 处理所有由Apahce服务器转发过来的Web客户请求.
- <Service name="Nginx" /> --- 处理所有由Nginx服务器转发过来的Web客户端请求.
2.1.3 使用NIO模式处理请求
1) 默认配置 - BlockingIO模型:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
2) 关于NIO的说明:
- 每个Web客户端请求对服务器端来说就是一个单独的线程, 客户端请求数量增多, 服务器端的处理线程数量也将增加, 对CPU而言, 将会在线程切换上消耗更多的时间. 而NIO则是使用单线程(单个CPU)或只使用少量的多线程(多CPU)来接受Socket, 而由线程池来处理堵塞在 Pipe 或 Queue 中的请求. 这样的话, 只要OS可以接受TCP连接, Web服务器就可以处理该请求 -- 大大提高了Web服务器的伸缩性.
- Tomcat 8 下使用 NIO2, 即
org.apache.coyote.http11.Http11Nio2Protocol
更优. - Tomcat 6、7 下使用 NIO, 即
org.apache.coyote.http11.Http11NioProtocol
更优.
3) NIO模型配置:
<Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" maxPostSize="10485760" acceptorThreadCount="2" />
4) 参数说明:
executor="..." # 连接器使用的线程池名称.
port="..." # 连接端口, URL中指定此端口进行访问.
protocol="..." # 连接器使用的请求处理模式.
redirectPort="8443" # 基于SSL的端口, 在需要基于安全通道的场合, 比如当客户端的请求协议是HTTPS时, 将该请求转发到此8443端口.
2.1.4 使用APR模式处理请求
可以简单地将APR模式理解为,Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库, 进行文件读取或网络传输操作, 从而大大地提高Tomcat对静态文件的处理性能.
APR是Tomcat上运行高并发应用的首选模式, 同时如果使用HTTPS方式传输, 也可以提升SSL的处理性能.
前面已经提到, APR模式会调用操作系统底层的IO接口, 所以需要安装必要的依赖.
1) 安装OpenSSL:
安装命令如下:
yum -y install openssl-devel
2) 安装APR组件:
- yum安装:
- yum -y install apr-devel apr apr-util tomcat-native
- 源码安装
- 需要下载的包:apr-<version>.tar.gz和apr-util-<version>.tar.gz
- 下载地址: http://mirrors.aliyun.com/apache/apr/
- 安装: 解压, 编译, 安装,在此啰嗦,不会的自己百度下.
- 配置环境变量: 上面源码安装完后设置下环境变量:
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/apr/lib
- 或者, 将/usr/local/apr/lib包路径添加到/etc/ld.so.conf文件中:
- echo "/usr/local/apr/lib" >> /etc/ld.so.conf
源码安装常见错误及解决:
可能出现gcc依赖没有安装的错误, 可通过 yum install gcc 命令安装.
如果make过程中出错, 解决错误后重新安装前需要执行清理: make clean, 然后再次尝试make及make install过程.
如果抛出 xml/apr_xml.c:35:19: error: expat.h: No such file or directory, 说明缺少了expat库, 可执行下属命令安装: yum install expat-devel.
3) 安装tomcat-native组件:
tomcat-native组件可以看作是Tomcat与APR交互的中间环节.
tomcat-native?是什么?前面没有叫下载啊?
确实,我刚开始在网上搜索的时候也是很困惑的,可是有一个人说了,“就在下载的tomcat的bin目录下面”,我去看了一下,果然有!!
将我们安装好的tomcat的bin目录下的 tomcat-native.tar.gz 文件复制到 /usr/local/src 中,并且解压缩,得到目录tomcat-native-<version>-src 在这个目录中有相关的说明,告诉我们如何构建。
进入到目录中的 jni/native 目录内,这个目录内的文件就是我们需要的文件,依次执行下面的命令
./configure --with-apr=/usr/local/apr --with-java-home=/usr/java/jdk --with-ssl=yes
make
make install
在这里,apr的目录要使用前面安装apr的时候的目录,如果修改了的话,还请对应修改,java的目录要使用jdk的根目录,如果不是这个也请修改。
执行上面的命令之后,会在目录
/usr/local/apr/lib
中生成对应的文件,可以查看文件,确认安装成功。也可以根据每一步执行命令的输出来判断成功没有,若有问题的话,要及时解决,在进行后续操作。
4) Tomcat整合APR:
-
第一步: 修改启动脚本
catalina.sh
:
在${TOMCAT_HOME}/bin/catalina.sh
文件的cygwin=false
前(110行左右)加入下述启动参数:JAVA_OPTS="$JAVA_OPTS -Djava.library.path=/usr/local/apr/lib"
-
第二步: 修改容器配置文件server.xml:
查看
${TOMCAT_HOME}/conf/server.xml
文件, 确保如下监听器没有被注释掉:<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
-
修改Connector选项:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" > </Connector>
5) 验证配置是否成功:
启动Tomcat, 在 ${TOMCAT_HOME}/logs/catalina.out
文件中查看日志信息:
-
如果出现下述内容, 说明APR组件安装不成功:
Sep 14, 2018 19:11:20 PM org.apache.catalina.core.AprLifecycleListener init INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path:...
-
如果出现下述内容, 说明APR组件安装成功:
Sep 14, 2018 19:19:47 PM org.apache.catalina.core.AprLifecycleListener init INFO: Loaded APR based Apache Tomcat Native library 1.1.27 using APR version 1.6.3. Sep 14, 2018 19:19:47 PM org.apache.catalina.core.AprLifecycleListener init INFO: APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true]. Sep 14, 2018 19:19:47 PM org.apache.catalina.core.AprLifecycleListener initializeSSL INFO: OpenSSL successfully initialized (OpenSSL 1.0.1e-fips 11 Feb 2013) Sep 14, 2018 19:19:47 PM org.apache.coyote.AbstractProtocol init INFO: Initializing ProtocolHandler ["http-apr-8080"] Sep 14, 2018 19:19:47 PM org.apache.coyote.AbstractProtocol init INFO: Initializing ProtocolHandler ["ajp-apr-8009"]
-
Tomcat通过APR模式成功启动:
Sep 14, 2018 19:19:56 PM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["http-apr-8986"] Sep 14, 2018 19:19:56 PM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["ajp-apr-8915"] Sep 14, 2018 19:19:56 PM org.apache.catalina.startup.Catalina start INFO: Server startup in 9421 ms
2.2 配置AJP连接器
AJP(Apache JServer Protocol)是为 Tomcat 与 HTTP 服务器之间通信而定制的协议, 能提供较高的通信速度和效率.
AJP v13 协议是面向包的, Web服务器和Servlet容器通过TCP连接来交互, 为了节省 创建Socket的昂贵代价, Web服务器会尝试维护一个永久的TCP连接到Servlet容器, 并在多个请求与响应周期过程内重用该TCP连接.
如果使用Apache架构, 就要用AJP连接器, 当Apache接收到动态网页请求时, 通过在配置中指定的端口号将请求发送给在此端口号上监听的AJP连接器组件.
如果不使用Tomcat + Apache, 而是用其他架构, 如Tomcat + Nginx, 就需要注销掉该连接器.
<!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> -->
3. setenv.sh相关配置
3.1 基于JDK7配置建议
在Linux环境下设置Tomcat JVM,在/opt/tomcat/bin/catalina.sh文件中找到"
# ----- Execute The Requested Command"位置,设置JVM如下: # ----- Execute The Requested Command ----------------------------------------- JAVA_OPTS="$JAVA_OPTS -server -Xms3072m -Xmx3072m -XX:PermSize=1024M -XX:MaxPermSize=1024M"
参数说明:
-Xms:设置JVM最小内存。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmx:设置JVM最大可用内存。
-XX:NewSize:设置年轻代大小
-XX:PermSize:设置永久代大小
-XX:MaxPermSize:设置最大永久代大小
JVM内存模型
1、Java栈
Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。
它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文。栈空间随着线程的终止而释放。
StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的。
2、堆
Java中堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等。
3、Java 的内存模型
a、Young,年轻代(易被 GC)
Young 区被划分为三部分,Eden 区和两个大小严格相同的 Survivor 区
其中 Survivor 区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在 Young 区间变满的时候,minor GC 就会将存活的对象移到空闲的Survivor 区间中,根据 JVM 的策略,在经过几次垃圾收集后,任然存活于 Survivor 的对象将被移动到 Tenured 区间。
b、Tenured,终身代
Tenured 区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在 Young 复制转移一定的次数以后,对象就会被转移到 Tenured 区,一般如果系统中用了 application 级别的缓存,缓存中的对象往往会被转移到这一区间。
c、Perm,永久代
主要保存 class,method,filed 对象,这部门的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到 java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的 class 没有被卸载掉,这样就造成了大量的 class 对象保存在了 perm 中,这种情况下,一般重新启动应用服务器可以解决问题。
如果服务器只运行一个 Tomcat:
机子内存如果是 8G,一般 PermSize 配置是主要保证系统能稳定起来就行:
JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms6144m -Xmx6144m -XX:NewSize=1024m -XX:MaxNewSize=2048m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"
机子内存如果是 16G,一般 PermSize 配置是主要保证系统能稳定起来就行:
JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms13312m -Xmx13312m -XX:NewSize=3072m -XX:MaxNewSize=4096m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"
机子内存如果是 32G,一般 PermSize 配置是主要保证系统能稳定起来就行:
JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms29696m -Xmx29696m -XX:NewSize=6144m -XX:MaxNewSize=9216m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"
如果是开发机:
-Xms550m -Xmx1250m -XX:PermSize=550m -XX:MaxPermSize=1250m
参数说明:
-Dfile.encoding:默认文件编码
-server:表示这是应用于服务器的配置,JVM 内部会有特殊处理的
-Xmx1024m:设置JVM最大可用内存为1024MB
-Xms1024m:设置JVM最小内存为1024m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-XX:NewSize:设置年轻代大小
-XX:MaxNewSize:设置最大的年轻代大小
-XX:PermSize:设置永久代大小
-XX:MaxPermSize:设置最大永久代大小
-XX:NewRatio=4:设置年轻代(包括 Eden 和两个 Survivor 区)与终身代的比值(除去永久代)。设置为 4,则年轻代与终身代所占比值为 1:4,年轻代占整个堆栈的 1/5
-XX:MaxTenuringThreshold=10:设置垃圾最大年龄,默认为:15。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
-XX:+DisableExplicitGC:这个将会忽略手动调用 GC 的代码使得 System.gc() 的调用就会变成一个空调用,完全不会触发任何 GC
3.2 基于JDK8配置建议
Tomcat容器是运行在JVM上的, 其默认内存一般都很小(物理内存的1/64), 在实际生产环境中, 若不配置则会极大浪费服务器资源, 影像系统的性能. 可以通过调整JVM启动参数, 使得Tomcat拥有更好的性能.
对于JVM的优化主要有两个方面: JVM内存调优 和 垃圾收集策略调优
3.2.1 JVM内存调优
Tomcat运行内存 = Xmx(初始内存大小) + Perm Generation(JDK 7中的永久代大小) + Java应用创建的线程数 * 1MB.
Java 应用每创建一个线程, JVM 进程的内存中就会创建一个 Thread 对象, 同时也会在操作系统中创建一个真正的物理线程(参考JVM规范), 操作系统会在 Tomcat 的空闲内存中创建这个物理线程, 而不是在 JVM 的 Xmx 堆内存中创建.
在 JDK 1.4中, 默认的栈大小是256KB/线程, 但自 JDK 5(为了推广的方便, JDK 后续版本不再是1.x命名)开始, 默认的栈大小变为1M/线程. 举例: 如果系统剩余内存为400M, 则Java应用最多能创建400个可用线程.
所以: 要想创建更多的线程, 必须减少分配给JVM的最大内存.
内存配置相关参数:
- -server
- JVM的server模式, 在多CPU服务器中性能可以得到更好地发挥. 默认为client. 配置server模式时要将其作为第一个参数.
- -Xmx4g
- 最大堆内存, 默认为物理内存的1/4(已在JDK 7下验证, 最大值为30638MB, 总内存126GB的23.75%).
- 在只运行Tomcar容器的服务器中, 建议设置为物理内存的50%~80%.
- -Xms4g
- 初始堆内存大小, 默认值为物理内存的1/64(已在JDK 7下验证, 内存126GB, 初始值为2GB).
- -Xss128k
- 每个线程的Stack大小. 在相同物理内存下, 减小这个值能生成更多的线程, 但是操作系统对一个进程内的线程数是有限制的, 经验范围是3000~5000.
- -XX:NewRatio=4
- 设置新生代(包括Eden和两个Survivor区)与老年代的比值(除去持久代), 默认为2, 即新生代与老年代所占比值为1:2, 新生代占整个堆栈的1/3.
- -XX:SurvivorRatio=4
- 设置新生代中Eden区与1个Survivor区的大小比值. 默认为8, 即Eden区占新生代的80%, 2个Survivor分别占新生代的10%.
- 设置为4, 则两个Survivor区与一个Eden区的比值为2:4, 一个Survivor区占整个新生代的1/6.
- -Xmn1024m
- 设置Young Generation所占用的Java Heap大小为1g. 此值对系统性能影响较大, Sun官方推荐配置为整个堆的3/8(或Xmx的1/4~1/3左右).
- 也可使用-XX:NewSize和-XX:MaxNewsize设置新生代的初始值和最大值.
- 注意: -Xmn 与 -XX:NewSize、-XX:MaxNewSize 的优先级: -XX:NewRatio的值会被忽略.
- 1. 高优先级: -XX:NewSize/-XX:MaxNewSize
- 2. 中优先级: -Xmn, 等效于同时设置 -Xmn = -XX:NewSize = -XX:MaxNewSize 三者的值
- 3. 低优先级: -XX:NewRatio
- -Xmn参数是在JDK 1.4 开始支持, 推荐使用之.
- -XX:NewSize=1g
- 设置新生代的大小, 默认为1.25MB(已在JDK 7下验证). 若显示设置此值, 将使得NewRatio选项失效.
- -XX:OldSize=2g
- 设置老年代的大小, 默认为5.1875MB(已在JDK 7下验证).
- -XX:PermSize=128m
- JDK 7及以下版本适用: 设置Java Heap中永久代的初始大小, 默认为20.75MB(已在JDK 7下验证, client、server模式下均相同).
- -XX:MaxPermSize=256m
- JDK 7及以下版本适用: 设置Java Heap中永久代的最大值. 默认为82.0MB(已在JDK 7下验证, client、server模式下均相同).
- -XX:MetaspaceSize=128m
- JDK 8及以上版本适用: 初始元空间的大小, 默认为21MB(实际为20.79MB左右, 已验证).
- -XX:MaxMetaspaceSize=256m
- JDK 8及以上版本适用: 最大元空间的大小. 默认无上限(在126GB物理内存的服务器中, 默认值为(2^44-1)MB, 已验证).
注意:
① 如果不指定Xmx、Xms和NewSize、OldSize, 则系统将基于Xms=1/64总内存大小, 对各个Space按照NewRatio=2进行空间的分配, 此时NewSize与OldSize的默认大小将失效.
② 如果指定了Xmx、Xms, 未指定NewSize、OldSize, 则系统将优先满足 NewRatio=2, 且OldSize+NewSize=Xms, 此时NewSize与OldSize的默认大小将失效.
结论: 除非显式指定NewSize与OldSize的值, 否则它们的默认配置一般都不会得到满足. 显式指定其中任一个, 另一个就会基于默认值, 并根据应用程序的消耗动态分配空间大小.
3.2.2 tomcat JVM内存调优
JVM内存方面的调优, 需要在${TOMCAT_HOME}/bin/catalina.sh文件中调整, 配置 JAVA_OPTS 变量即可. 在启动Tomcat时, 会执行catalina.sh中的脚本, 将 JAVA_OPTS 作为JVM的启动参数进行处理.
具体可参考如下配置(示例服务器配置: 126g的物理内存, 2个10核心20线程的物理CPU):
在文件最前面(即cygwin=false之前)设置, $JAVA_OPTS 的作用是保留原有的设置, 防止此次修改覆盖之前的设置
JAVA_OPTS="$JAVA_OPTS -Xmx96g -Xms96g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
1) 说明:
① 若不指定Xmx, 即Java Heap的最大值, 程序启动时, JVM会调整其大小以满足程序运行的需要. 每次调整时, 都会对堆进行一次完全垃圾收集(即Full GC), 比较影响性能. 因此推荐明确指定Xmx的大小.
② 令Xmx=Xms会有更好地性能表现: 能避免JVM在每次垃圾收集后重新动态调节堆空间, 因为频繁伸缩堆大小将带来额外的性能消耗.
③ JVM有2种模式: client客户端模式 和 server服务端模式 , 平时开发中使用的多是默认的client模式. 可通过命令行参数-server强制开启server模式. 两者之间的最大区别是, JVM对server模式做了大量优化: 虽然server模式下应用程序启动较慢, 但在长时间运行下, 程序运行效率会明显高于client模式, 即 client模式不适合需要长时间运行的项目 .
2) 元空间的调优:
元空间的大小将受限于机器的内存的限制. 限制类的元数据的内存大小, 以避免出现虚拟内存切换以及本地内存分配失败.
如果可能出现类加载器泄漏, 应当配置此参数指定大小. 32位机器上, 如果地址空间可能会被耗尽, 也应当配置此参数.
元空间的初始大小是21M——这是GC的初始高水位线, 超过这个大小会进行Full GC来进行类的收集.
如果启动后GC过于频繁, 请将该值设置得大一些, 以便推迟GC的执行时间.
3.2.3 JVM垃圾回收(GC)策略调优
Tomcat的GC策略一般都是与其内存参数一起配置的, 与应用复杂度相匹配的GC策略、与服务器性能相适应的内存比例, 都将使得系统性能得到大幅提升.
GC策略方面的调优, 也是在 ${TOMCAT_HOME}/bin/catalina.sh文件中调整 -- 同样是配置 JAVA_OPTS 变量即可.
JAVA_OPTS="$JAVA_OPTS -Xmx3550m -Xms3550m -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100"
参数说明:
- -XX:+UseSerialGC: 设置串行收集器(JDK1.5以前主要的回收方式)
- -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
- -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
- -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集
- -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
- -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
# 并行收集器(吞吐量优先) java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 # 并发收集器(响应时间优先) java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC
参数说明:
- -XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
- -XX:+UseParNewGC: 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
- -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
- -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
以JDK 8、Tomcat 8为例, 服务器内存为128GB, 作出如下配置:
通过Solr集群大批量导入数据的应用中, Parallel GC策略的暂停时间太长, 所以选择CMS收集器.
# 下述配置各自独占一行, 要置于"cygwin=false"之前. # 配置内存 JAVA_OPTS="-server -Xmx96g -Xms96g -Xmn35g -XX:OldSize=55g -Xss128k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m" # 配置GC策略 JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 -XX:CMSInitiatingOccupancyFraction=40 -XX:CMSFullGCsBeforeCompaction=0 -XX:+ExplicitGCInvokesConcurrent -XX:SoftRefLRUPolicyMSPerMB=0 -XX:MaxGCPauseMillis=100 -Xnoclassgc"
其中Java Heap的初始大小和最大大小均设置为96g, 76%的物理内存(以不超过80%为宜).
小结:
在内存设置中需要做一下权衡
1)内存越大,一般情况下处理的效率也越高,但同时在做垃圾回收的时候所需要的时间也就越长,在这段时间内的处理效率是必然要受影响的。
2)在大多数的网络文章中都推荐 Xmx和Xms设置为一致,说是避免频繁的回收,这个在测试的时候没有看到明显的效果,内存的占用情况基本都是锯齿状的效果,所以这个还要根据实际情况来定。