Apache Tomcat 8.5 安全配置与高并发优化

Posted by Sunday on 2018-07-11

Java的内存模型

  • Young,年轻代(易被 GC)。Young 区被划分为三部分,Eden 区和两个大小严格相同的 Survivor 区,其中 Survivor 区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在 Young 区间变满的时候,minor GC 就会将存活的对象移到空闲的Survivor 区间中,根据 JVM 的策略,在经过几次垃圾收集后,任然存活于 Survivor 的对象将被移动到 Tenured 区间。

  • Tenured,老年代。Tenured 区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在 Young 复制转移一定的次数以后,对象就会被转移到 Tenured 区,一般如果系统中用了 application 级别的缓存,缓存中的对象往往会被转移到这一区间。

  • Perm,永久代。主要保存 class,method,filed 对象,这部门的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到 java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的 class 没有被卸载掉,这样就造成了大量的 class 对象保存在了 perm 中,这种情况下,一般重新启动应用服务器可以解决问题。

JVM内存调整

vim /usr/local/tomcat/bin/catalina.sh

1
JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms4096m -Xmx4096m -XX:NewSize=512m -XX:MaxNewSize=512m "

JVM 常用参数详解
-Dfile.encoding:默认文件编码
server:一定要作为第一个参数,在多个CPU时性能佳
-Xms: JVM最小内存,此值可以设置与-Xmx相同,避免每次垃圾回收完成后JVM重新分配内存。
-Xmx:JVM最大可用内存(堆内存),一般不大于物理内存的80%
-Xmn:设置JVM新生代大小(JDK1.4之后版本)。一般-Xmn的大小是-Xms的1/2左右,不要设置的过大或过小,过大导致老年代变小,频繁Full GC,过小导致minor GC频繁。如果不设置-Xmn,可以采用-XX:NewRatio=2来设置,也是一样的效果
-XX:NewSize :年轻代初始大小
-XX:MaxNewSize :年轻代占整个堆内存的最大值
-XX:PermSize:永久代(非堆)初始大小,tomcat 8此参数已经移除
-XX:MaxPermSize:永久代占整个堆内存的最大值,tomcat8 此参数已经移除
-XX:NewRatio:设置年轻代(包括 Eden 和两个 Survivor 区)与终身代的比值(除去永久代)。设置为 4,则年轻代与老年代所占比值为 1:4,年轻代占整个堆栈的 1/5
-XX:MaxTenuringThreshold:设置垃圾最大年龄,默认为:15。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
-XX:+DisableExplicitGC:这个将会忽略手动调用 GC 的代码使得 System.gc() 的调用就会变成一个空调用,完全不会触发任何 GC

如果服务器只运行一个 Tomcat
机子内存如果是 8G,一般 PermSize 配置是主要保证系统能稳定起来就行:
JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms6144m -Xmx6144m -XX:NewSize=1024m -XX:MaxNewSize=2048m -XX:PermSize=512m -XX:MaxPermSize=512m -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:PermSize=512m -XX:MaxPermSize=512m -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:PermSize=1024m -XX:MaxPermSize=1024m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"

如果是开发机
-Xms550m -Xmx1250m -XX:PermSize=550m -XX:MaxPermSize=1250m

涉及堆(Heap)和非堆(Non-Heap) 内存知识
堆内存是存储对象,例如类实例、数组等,在JVM中堆之外的内存称为非堆内存。可以理解为非堆内存留给JVM 自己用。
堆中存的是对象,栈中存的是基本数据类型和堆中对象的引用。在栈中,一个对象对应一个4byte的引用。
栈是运行时的单位,而堆是存储的单位。

垃圾回收

垃圾回收(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

maxThreads连接数

默认值:

1
2
3
4
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->

修改为:

1
2
3
4
5
6
7
8
9
<Executor
name="tomcatThreadPool"
namePrefix="catalina-exec-"
maxThreads="500"
minSpareThreads="100"
maxIdleTime="60000"
prestartminSpareThreads = "true"
maxQueueSize = "100"
/>

参数解释:
maxThreads:最大并发数,默认设置 200,一般建议在 500 ~ 800,根据硬件设施和业务来判断
minSpareThreads:Tomcat 初始化时创建的线程数,默认设置 25
maxIdleTime:如果当前线程大于初始化线程,那空闲线程存活的时间,单位毫秒,默认60000=60秒=1分钟。
prestartminSpareThreads:在 Tomcat 初始化的时候就初始化 minSpareThreads 的参数值,如果不等于 true,minSpareThreads 的值就没啥效果了
maxQueueSize:最大的等待队列数,超过则拒绝请求
http://tomcat.apache.org/tomcat-8.5-doc/config/executor.html

Connector 参数优化

vim /usr/local/tomcat/conf/server.xml

1
2
3
4
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
/>

修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
maxConnections="10000"
redirectPort="8443"
enableLookups="false"
acceptCount="100"
maxPostSize="10485760"
maxHttpHeaderSize="8192"
disableUploadTimeout="true"
acceptorThreadCount="2"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript"
URIEncoding="utf-8"
processorCache="20000"
tcpNoDelay="true"
connectionLinger="5"
server="Sun Server 7"
/>

参数解释:
connectionTimeout:Connector接受一个连接后等待的时间(milliseconds),默认值是60000。
maxConnections:这个值表示最多可以有多少个socket连接到tomcat上
enableLookups:禁用DNS查询
acceptCount:当tomcat起动的线程数达到最大时,接受排队的请求个数,默认值为100。
maxPostSize:设置由容器解析的URL参数的最大长度,-1(小于0)为禁用这个属性,默认为2097152(2M) 请注意, FailedRequestFilter 过滤器可以用来拒绝达到了极限值的请求。
maxHttpHeaderSize:http请求头信息的最大程度,超过此长度的部分不予处理。一般8K。
disableUploadTimeout:这个标志允许servlet容器使用一个不同的,通常长在数据上传连接超时。 如果不指定,这个属性被设置为true,表示禁用该时间超时。
acceptorThreadCount:用于接受连接的线程数量。增加这个值在多CPU的机器上,尽管你永远不会真正需要超过2。 也有很多非维持连接,您可能希望增加这个值。默认值是1。
compression:是否启用GZIP压缩 on为启用(文本数据压缩) off为不启用, force 压缩所有数据
compressionMinSize:最小压缩大小,单位Byte
compressableMimeType:压缩的数据类型
URIEncoding:网站一般采用UTF-8作为默认编码。
processorCache:协议处理器缓存的处理器对象来提高性能。 该设置决定多少这些对象的缓存。-1意味着无限的,默认是200。 如果不使用Servlet 3.0异步处理,默认是使用一样的maxThreads设置。 如果使用Servlet 3.0异步处理,默认是使用大maxThreads和预期的并发请求的最大数量(同步和异步)。
tcpNoDelay:如果设置为true,TCP_NO_DELAY选项将被设置在服务器套接字,而在大多数情况下提高性能。这是默认设置为true。
connectionLinger:秒数在这个连接器将持续使用的套接字时关闭。默认值是 -1,禁用socket 延迟时间。
server:隐藏Tomcat版本信息,首先隐藏HTTP头中的版本信息

Tomcat 6、7 设置 nio 更好:protocol:org.apache.coyote.http11.Http11NioProtocol
Tomcat 8 设置 nio2 好:protocol:org.apache.coyote.http11.Http11Nio2Protocol
Tomcat 8 设置 APR 性能更好:protocol:org.apache.coyote.http11.Http11AprProtocol
Tomcat 使用APR设置: http://www.sundayle.com/2018/07/12/tomcat-apr/

Tomcat安全设置

禁用8005端口

vim /usr/local/Tomcat/conf/server.xml

默认值:

1
><Server port="8005" shutdown="SHUTDOWN">

修改为:

1
<Server port="-1" shutdown="SHUTDOWN">

或修改为其他端口和shutdown

1
<Server port="8015" shutdown="SHUTDOWNXXXX">

修改AJP端口

默认值:

1
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

修改为:

1
<Connector port="8019" protocol="AJP/1.3" redirectPort="8443" />

使用普通用户启动tomcat

1
2
useradd -s /bin/false tomcat
sudo -u tomcat /bin/bash $tomcat_HOME/bin/startup.sh

关闭自动部署

防止被植入木马等恶意程序

默认值:

1
2
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">

修改为:

1
2
<Host name="localhost" appBase="webapps"
unpackWARs="false" autoDeploy="false" reloadable="false">

注释或删除tomcat-user.xml所有用户权限

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
</tomcat-users>

隐藏或修改tomcat版本号

1
2
3
4
cd /usr/local/tomcat/lib/
unzip catalina.jar
cd org/apache/catalina/util
vim ServerInfo.properties
1
2
3
4
5
6
7
8
9
10
11
12
server.info=Apache Tomcat/8.5.32
server.number=8.5.32.0
server.built=Jun 20 2018 19:50:35 UTC

<!--
server.info=Apache tomcat/8.5.43
server.number=8.5.43.0
server.built=Jul 4 2019 20:53:15 UTC
-->
server.info=EnjoyMusic
server.number=x.x.x.x
server.built=Today

将以上去掉或修改信息,压缩回jar包

1
jar uvf catalina.jar org/apache/catalina/util/ServerInfo.properties

APR 库性能优化

http://www.sundayle.com/2018/07/12/tomcat-apr/

动静分离

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

TomcatSessionID持久化三种方法:

Session粘性:通过浏览器Cookie绑定SessionID ,通过sticky模式将同一Session请求分配到同一Tomcat上。
Session复制:Tomcat通过广播形式将Session 同步到其他Tomcat节点,并且Linux下要手动开启开放广播地址。不易后端节点过多
Session保存数据库(memcache、redis ):将SessionID保存在共享的数据库中。

Out of Memory

OOM(Out of Memory)异常常见有以下几个原因:
老年代内存不足:java.lang.OutOfMemoryError:Javaheapspace
永久代内存不足:java.lang.OutOfMemoryError:PermGenspace
代码bug,占用内存无法及时回收。

1)错误提示:java.lang.OutOfMemoryError:Java heap space

Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,有可能导致系统无法运行。常见的问题是报Tomcat内存溢出错误,Outof Memory(系统内存不足)的异常,从而导致客户端显示500错误,一般调整Tomcat的-Xms和-Xmx即可解决问题,通常将-Xms和-Xmx设置成一样,堆的最大值设置为物理可用内存的最大值的80%。

set JAVA_OPTS=-Xms512m-Xmx512m
2)错误提示:java.lang.OutOfMemoryError: PermGenspace

PermGenspace的全称是Permanent Generationspace,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGenspace中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGenspace进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行precompile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。解决方法:

setJAVA_OPTS=-XX:PermSize=128M
3)在使用-Xms和-Xmx调整tomcat的堆大小时,还需要考虑垃圾回收机制。如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过3-5 秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的 80% 作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。

https://renwole.com/archives/357
https://www.jianshu.com/p/c8613d17e5fe
http://www.zyops.com/java-tomcat/