Tomcat 配置及优化

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接口, 所以需要提前安装必要的依赖, 具体配置方法见下文描述.

 BIONIONIO2APR
类名 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设置为一致,说是避免频繁的回收,这个在测试的时候没有看到明显的效果,内存的占用情况基本都是锯齿状的效果,所以这个还要根据实际情况来定。

 

posted @ 2019-11-15 15:29  冷水泡茶  阅读(674)  评论(0编辑  收藏  举报