Tomcat部署及优化

自2017年11月编程语言排行榜Java占比13%,高居榜首,Tomcat也一度成为Java开发人员的首选。其开源、占用系统资源少、跨平台等特性被深受喜爱。本章主要学习如何部署Tomcat服务,根据生产环境实现多个虚拟主机的配置,最后的重点是进行压测,根据压测结果如何优化Tomcat服务及常见的内存溢出如何处理。

Tomcat

1、Tomcat介绍

自从JSP(Java Server Pages)发布之后,推出了各式各样的JSP引擎。Apache Group在完成GNUJSP1.0的开发以后,开始考虑在SUN的JSWDK(JavaServer Web Development Kit)基础上开发一个可以直接提供Web服务的JSP服务器,当然同时也支持Servlet, 这样Tomcat就诞生了。

Tomcat是Apache软件基金会( Apache Software Foundation)的Jakarta项目中的一个核心项目,由Apache、Sun和其他一些公司及个人共同开发而成。其被JavaWorld杂志的编辑选为2001年度最具创新的Java产品,同时它又是sun公司官方推荐的Servlet和JSP容器,因此其越来越多的受到软件公司和开发人员的喜爱。由于有了Sun的参与和支持,最新的Servlet和JSP规范总是能在Tomcat中得到体现。因为Tomcat技术先进、性能稳定,而且免费,因而深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服务器。

Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache服务器,可利用它响应HTML(标准通用标记语言下的一个应用) 页面的访问请求。实际上,Tomcat是Apache服务器的扩展,但运行时它是独立运行的,所以当运行Tomcat时,它实际上作为一个与Apache独立的进程单独运行的。

当配置正确时,Apache为HTML页面服务,而Tomcat实际上运行JSP页面和Servlet。另外,Tomcat和IIS等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache服务器。

2、Tomcat核心组件

通常意义上的Web服务器接受请求后,只是单纯地响应静态资源,如HTML文件,图片文件等,不能在后端进行一定的处理操作。Tomcat是Apache下的一个子项目,它具备Web服务器的所有功能,不仅可以监听接受请求并响应静态资源,而且可以在后端运行特定规范的Java代码Servlet,同时将执行的结果以HTML代码的形式写回客户端。Tomcat由一系列的组件构成,其中核心的组件有三个:

  • Web容器: 完成Web服务器的功能。
  • Servlet容器: 名字为catalina,用于处理Servlet代码。
  • JSP容器: 用于将JSP动态网页翻译成Servlet代码。

3、Tomcat总体结构

Tomcat的结构很复杂,但是Tomcat也非常的模块化,下面是Tomcat的总体结构图:
image.png

Tomcat 的心脏是两个组件:Connector和Container。Connector组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个Container可以选择对应多个Connector。多个Connector和一个Container就形成了一个Service,Service还要一个生存的环境,那就是Server,所以整个Tomcat的生命周期由Server控制。

Connector主要负责对外交流,Container主要处理Connector接受的请求,主要是处理内部事务。是Service将它们连接在一起,Service只是在Connector 和Container外面多包一层,把它们组装在一起,向外面提供服务,一个Service可以设置多个Connector,但是只能有一个Container容器。

Connector 组件是Tomcat中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的tcp连接请求,创建一个Request和Response对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的Request 和Response对象传给处理这个请求的线程,处理这个请求的线程就是Container 组件要做的事了。

Container是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine包含Host,Host包含Context,Context包含Wrapper。通常一个Servlet class对应一个Wrapper,如果有多个Servlet就可以定义多个Wrapper,如果有多个Wrapper就要定义一个更高的Container了,如Context。

Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。

Engine容器比较简单,它只定义了一些基本的关联关系。

Host 是 Engine 的字容器,一个Host在Engine中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。

Context代表Servlet的Context,它具备了Servlet运行的基本环境,理论上只要有Context就能运行Servlet了。简单的Tomcat可以没有Engine和Host。

Wrapper代表一个Servlet,它负责管理一个Servlet,包括的Servlet的装载、初始化、执行以及资源回收。Wrapper是最底层的容器,它没有子容器。

Tomcat 还有其它重要的组件,如安全组件security、logger日志组件、session、mbeans、naming 等其它组件。这些组件共同为Connector和Container 提供必要的服务。

Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的

4、Tomcat处理请求过程

  1. 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/11.1 Connector获得;
  2. Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应;
  3. Engine获得请求localhost/yy/index.JSP,匹配它所拥有的所有虚拟主机Host;
  4. Engine匹配到名为localhost的Host。即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机;
  5. localhost Host获得请求/yy/index.JSP,匹配它所拥有的所有Context;
  6. Host匹配到路径为/yy的Context。如果匹配不到,就把该请求交给路径名为”“的Context去处理;
  7. path=”/yy”的Context获得请求/index.JSP,在它的mapping table中寻找对应的Servlet;
  8. Context匹配到URL PATTERN为*.JSP的Servlet,对应于JSPServlet类;
  9. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JSPServlet的doGet()或doPost()方法;
  10. Context把执行完了之后的HttpServletResponse对象返回给Host;
  11. Host把HttpServletResponse对象返回给Engine;
  12. Engine把HttpServletResponse对象返回给Connector;
  13. Connector把HttpServletResponse对象返回给客户browser。

再看一张更简洁的图:

Tomcat部署及优化 - 图2

案例实施

1、下载并安装JDK

JDK(Java Development Kit)是Java语言的软件开发工具包(sdk)。它是一种用于构建在Java平台上发布的应用程序、applet和组件的开发环境;它是给程序开发者提供的开发工具箱,是一种最基本的工具。是整个java开发的核心,它除了包括完整的JRE(Java Runtime Environment),还包含了JAVA的运行环境(JVM+Java系统类库)和其他供开发者使用的JAVA工具。

oracle官方下载界面:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

  1. [root@www ~]# ls jdk-8u221-linux-x64.rpm
  2. jdk-8u221-linux-x64.rpm
  3. [root@www ~]# rpm -ivh jdk-8u221-linux-x64.rpm
  4. warning: jdk-8u221-linux-x64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
  5. Preparing... ################################# [100%]
  6. Updating / installing...
  7. 1:jdk1.8-2000:1.8.0_221-fcs ################################# [100%]
  8. Unpacking JAR files...
  9. tools.jar...
  10. plugin.jar...
  11. javaws.jar...
  12. deploy.jar...
  13. rt.jar...
  14. jsse.jar...
  15. charsets.jar...
  16. localedata.jar...

jdk安装目录在/usr/java/jdk1.8.0_221-amd64,设置jdk的环境变量,编辑/etc/profile 文件, 增加如下内容:

  1. [root@www ~]# vim /etc/profile
  2. export JAVA_HOME=/usr/java/jdk1.8.0_221-amd64
  3. export CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
  4. export PATH=$JAVA_HOME/bin:$PATH
  5. [root@www ~]# source /etc/profile #令修改生效

查看jdk版本号,已经显示是我们安装的软件版本

  1. [root@www ~]# java -version
  2. java version "1.8.0_221"
  3. Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
  4. Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

2、安装启动Tomcat

从Tomcat官网下载apache-tomcat-9.0.22.tar.gz稳定二进制版本

  1. [root@www ~]# wget https://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.22/bin/apache-tomcat-9.0.22.tar.gz

将安装包移动到/usr/local下面,并重命名为tomcat

  1. [root@www ~]# tar zxvf apache-tomcat-9.0.22.tar.gz
  2. [root@www ~]# mv apache-tomcat-9.0.22 /usr/local/tomcat

启动Tomcat

  1. [root@www ~]# /usr/local/tomcat/bin/startup.sh
  2. Using CATALINA_BASE: /usr/local/tomcat
  3. Using CATALINA_HOME: /usr/local/tomcat
  4. Using CATALINA_TMPDIR: /usr/local/tomcat/temp
  5. Using JRE_HOME: /usr/java/jdk1.8.0_221-amd64
  6. Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
  7. Tomcat started.

在防火墙中放行8080端口

  1. [root@www ~]# firewall-cmd --add-port=8080/tcp --permanent
  2. success
  3. [root@www ~]# firewall-cmd --reload
  4. success

访问Tomcat,浏览器打开http://192.168.154.142:8080进行访问会出现Tomcat主页

Tomcat部署及优化 - 图3

3、Tomcat安装后目录结构

安装后目录结构如下所示:

  1. [root@www ~]# ls -l /usr/local/tomcat/
  2. total 124
  3. drwxr-x--- 2 root root 4096 Jul 22 17:04 bin
  4. -rw-r----- 1 root root 18982 Jul 4 22:23 BUILDING.txt
  5. drwx------ 3 root root 254 Jul 22 17:33 conf
  6. -rw-r----- 1 root root 5407 Jul 4 22:23 CONTRIBUTING.md
  7. drwxr-x--- 2 root root 4096 Jul 22 17:04 lib
  8. -rw-r----- 1 root root 57092 Jul 4 22:23 LICENSE
  9. drwxr-x--- 2 root root 197 Jul 22 17:33 logs
  10. -rw-r----- 1 root root 2333 Jul 4 22:23 NOTICE
  11. -rw-r----- 1 root root 3255 Jul 4 22:23 README.md
  12. -rw-r----- 1 root root 6852 Jul 4 22:23 RELEASE-NOTES
  13. -rw-r----- 1 root root 16262 Jul 4 22:23 RUNNING.txt
  14. drwxr-x--- 2 root root 30 Jul 22 17:04 temp
  15. drwxr-x--- 7 root root 81 Jul 4 22:20 webapps
  16. drwxr-x--- 3 root root 22 Jul 22 17:33 work

bin

存放启动和关闭Tomcat的脚本文件,比较常用的是 catalina.sh、startup.sh、shutdown.sh 三个文件;

conf

存放Tomcat服务器的各种配置文件,比较常用的是server.xml、context…xml、tomcat-users.xml、web.xml四个文件;

lib

存放Tomcat服务器的jar包,一般不作任何改动,除非连接第三方服务,比如 redis,那就需要添加相对应的jar包;

logs

存放Tomcat日志;

temp

存放Tomcat运行时产生的文件;

webapps

存放项目资源的目录;

work

Tomcat工作目录, 一般清除Tomcat缓存的时候会使用到;

4、虚拟主机配置

有时候公司会有多个项目需要运行,那么肯定不可能是一台服务器上运行多个Tomcat服务,这样会消耗太多的系统资源。此时,就需要使用到Tomcat虚拟主机。例如现在新增两个域名 www.test.com 和 bbs.test.com,希望通过这两个域名访问到不同的项目内容。

创建 www 和 bbs 项目目录和文件

执行下面的命令, 可以创建 www 和 bbs 项目目录和文件

  1. [root@www ~]# mkdir /usr/local/tomcat/webapps/www
  2. [root@www ~]# echo 'this is www page\!' > /usr/local/tomcat/webapps/www/index.jsp
  3. [root@www ~]# mkdir /usr/local/tomcat/webapps/bbs
  4. [root@www ~]# echo 'this is bbs page\!' > /usr/local/tomcat/webapps/bbs/index.jsp

修改 Tomcat 主配置文件

修改 Tomcat 主配置文件/usr/local/tomcat/conf/server.xml,在下面增加如下内容:

  1. [root@www ~]# vim /usr/local/tomcat/conf/server.xml
  2. <Host name="www.test.com" appBase="/usr/local/tomcat/webapps"
  3. unpackWARs="true" autoDeploy="true" xmlValidation="false"
  4. xmlNamespaceAware="false">
  5. <Context docBase="/usr/local/tomcat/webapps/www"
  6. path="" reloadable="true" />
  7. </Host>
  8. <Host name="bbs.test.com" appBase="/usr/local/tomcat/webapps"
  9. unpackWARs="true" autoDeploy="true" xmlValidation="false"
  10. xmlNamespaceAware="false">
  11. <Context docBase="/usr/local/tomcat/webapps/bbs"
  12. path="" reloadable="true" />
  13. </Host>
  14. [root@www ~]# /usr/local/tomcat/bin/shutdown.sh
  15. [root@www ~]# /usr/local/tomcat/bin/startup.sh

测试虚拟主机

在客户端上使用域名访问:http://www.test.com:8080和http://bbs.test.com:8080 可以看到不同的网页内容

5、tomcat优化

Tomcat 的缺省配置并不适合生产环境,它会频繁出现假死现象需要重启,只有通过不断压测优化才能让它最高效率稳定的运行。本节将配合jmeter压测工具进行调优前和调优后的数据进行比较。

Tomcat 配置文件参数优化

Tomcat 主配置文件server.xml里面很多默认的配置项,但并不能满足业务需求,常用的优化相关参数如下:

  • maxThreads:Tomcat 使用线程来处理接收的每个请求,这个值表示 Tomcat可创建的最大的线程数,默认值是200。
  • minSpareThreads:最小空闲线程数,Tomcat启动时的初始化的线程数,表示即使没有人使用也开这么多空线程等待,默认值是 10
  • maxSpareThreads: 最大备用线程数, 一旦创建的线程超过这个值, Tomcat 就会关闭不再需要的socket线程。默认值是-1(无限制)。一般不需要指定
  • URIEncoding:指定Tomcat容器的URL编码格式,语言编码格式这块倒不如其它 Web 服务器软件配置方便,需要分别指定
  • connnectionTimeout:网络连接超时,单位:毫秒,设置为0表示永不超时,这样设置有隐患的。通常默认20000毫秒就可以。
  • enableLookups:是否反查域名,以返回远程主机的主机名,取值为: true 或 false,如果设置为false,则直接返回IP地址,为了提高处理能力,应设置为 false
  • disableUploadTimeout: 上传时是否使用超时机制。应设置为 true
  • connectionUploadTimeout:上传超时时间,毕竟文件上传可能需要消耗更多的时间,这个根据你自己的业务需要自己调,以使Servlet有较长的时间来完成它的执行,需要与上一个参数一起配合使用才会生效
  • acceptCount:指定当所有可以使用的处理请求的线程数都被使用时, 可传入连接请求的最大队列长度,超过这个数的请求将不予处理,默认为100 个
  • compression:是否对响应的数据进行GZIP压缩。off:表示禁止压缩;on:表示允许压缩(文本将被压缩);force:表示所有情况下都进行压缩。默认值为off,压缩数据后可以有效的减少页面的大小,一般可以减小1/3 左右,节省带宽
  • compressionMinSize:表示压缩响应的最小值,只有当响应报文大于等于这个值的时候才会对报文进行压缩,如果开启了压缩功能,默认值就是 2048
  • compressableMimeType:压缩类型,指定对哪些类型的文件进行数据压缩
  • noCompressionUserAgents=“gozilla, traviata”: 对于以下的浏览器, 不启用压缩

如果已经对代码进行了动静分离,静态页面和图片等数据就不需要Tomcat处理了,那么也就不需要在Tomcat中配置压缩了。

以上是一些常用的配置参数,还有好多其它的参数设置,还可以继续深入的优化,HTTP Connector与AJP Connector的参数属性值,可以参考官方文档的详细说明进行学习。链接地址 http://tomcat.apache.org/tomcat-9.0-doc/config/http.html

要压测,首先学习关于 jmeter 压测工具基本的使用方法。执行步骤如下:

1. 客户端安装 jdk,可以从 Oracle 官方下载,安装过程直接下一步即可!

2. 安装 jmeter 软件,下载地址: https://jmeter.apache.org/download_jmeter.cgi

3. 运行jemter:

4. 构建web测试计划

请求总数=用户数 x requests数 x 重复次数

  1. 添加线程组(Thread Group): 右键“test plan”—>“add”—>“threads (users)”—>“thread group”

Tomcat部署及优化 - 图4

  • number of threads: 表示创建多少个用户
  • ramp-up period: 表示创建以上用户需要的时间
  • loop count: 表示重复以上设置的次数
  • 如果设置用户4000,时间设为20,表示并发为200(4000/20)
  1. 添加默认的HTTP请求属性:选择刚创建的线程组,然后右键依次: Add → Config Element → HTTP Request Defaults。

Tomcat部署及优化 - 图5

  1. 添加Cookie支持:几乎所有的Web测试都应使用cookie支持,除非您的应用程序明确不使用cookie。要添加cookie支持,只需将HTTP Cookie Manager添加 到测试计划中的每个线程组。这将确保每个线程都有自己的cookie,但会在所有HTTP Request对象之间共享。要添加HTTP Cookie管理器,只需选择“ 线程组”,然后从“编辑”菜单或右键单击弹出菜单中选择Add → Config Element → HTTP Cookie Manager
  2. 添加HTTP请求:这个可以添加多个请求页面

Tomcat部署及优化 - 图6

  1. 添加侦听器以查看存储测试结果
  2. 添加查看结果树
  3. 添加聚合报告

Tomcat部署及优化 - 图7

优化参数

打开Tomcat主配置文件server.xml,找到默认配置进行修改:

  1. [root@www conf]# vim server.xml
  2. <Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
  3. connectionTimeout="20000"
  4. redirectPort="8443" minSpareThreads="50"
  5. enableLookups="false" disableUploadTimeout="true"
  6. acceptCount="300" maxThreads="500" processorCache="500"
  7. URIEncoding="UTF-8"
  8. compression="on"
  9. compressionMinSize="2048"
  10. compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/gif,image/jpg,image/png"/>

重新启动Tomcat服务器,jmeter还是继续保持同样的参数进行压测,优化后压测结果:

Tomcat部署及优化 - 图8

JVM优化

Tomcat 启动命令行中的优化参数, 就是 JVM 的优化 。 Tomcat 首先跑在 JVM 之上的, 因为它的启动其实也只是一个Java命令行,首先我们需要对这个 Java 的启动命令行进行调优。不管是YGC(对新生代堆进行gc)还是Full GC(全堆范围的gc)、GC(Grabage Collection 垃圾回收)都会导致程序运行中断,正确的选择不同的GC策略,调整JVM、GC 的参数, 可以极大的减少由于GC工作而导致的程序运行中断方面的问题,进而适当的提高Java程序的工作效率。但是调整GC是以个极为复杂的过程,由于各个程序具备不同的特点,如Web和GUI程序就有很大区别(Web可以适当的停顿, 但GUI停顿是客户无法接受的),而且由于跑在各个机器上的配置不同(主要 CPU 个数,内存不同),所以使用的GC种类也会不同。 下面对JVM 参数做比较详细的说明。

Tomcat 的启动参数位于安装目录${JAVA_HOME}/bin目录下,Linux操作系统就是 catalina.sh 文件。 Java_OPTS就是用来设置JVM相关运行参数的变量, 下面具体看JVM常用参数详解:

  • -server: 一定要作为第一个参数,只要Tomcat是运行在生产环境中, 这个参数必须给加上, 不然后面的参数不会生效;
  • -Xms: 表示Java初始化堆的大小, -Xms 与-Xmx 设成一样的值, 避免 JVM 反复重新申请内存, 导致性能大起大落, 默认值为物理内存的 1/64, 默认(MinHeapFreeRatio 参数可以调整) 空余堆内存小于 40%时, JVM 就会增大堆直到 -Xmx 的最大限制;
  • -Xmx: 表示最大Java堆大小,当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃,因此一般建议堆的最大值设置为物理内存的最大值的50%;
  • -XX:NewSize: 设置新生代内存大小;
  • -XX:MaxNewSize: 设置最大新生代新生代内存大小;
  • -XX:PermSize: 设置持久代内存大小;
  • -XX:MaxPermSize:设置最大值持久代内存大小,永久代不属于堆内存, 堆内存只包含新生代和老年代;
  • XX:+AggressiveOpts: 作用如其名(aggressive),启用这个参数, 则每当JDK版本升级时,你的JVM都会使用最新加入的优化技术(如果有的话);
  • -XX:+UseBiasedLocking:启用一个优化了的线程锁,我们知道在我们的 appserver,每个http请求就是一个线程,有的请求短有的请求长,就会有请求排队的现象,甚至还会出现线程阻塞,这个优化了的线程锁使得你的appserver内对线程处理自动进行最优调配;
  • -XX:+DisableExplicitGC:在程序代码中不允许有显示的调用“System.gc()”。 每次在到操作结束时手动调用System.gc()一下,付出的代价就是系统响应时间严重降低,就和关于Xms,Xmx里的解释的原理一样,这样去调用 GC导致系统的JVM大起大落;
  • -XX:+UseParNewGC: 对新生代采用多线程并行回收, 这样收得快, 注意最新的JVM版本,当使用-XX:+UseConcMarkSweepGC时,-XX:UseParNewGC 会自动开启。 因此,如果年轻代的并行GC不想开启,可以通过设置-XX:-UseParNewGC 来关掉;
  • -XX:MaxTenuringThreshold: 设置垃圾最大年龄。 如果设置为 0 的话, 则新生代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用(需要大量常驻内存的应用),可以提高效率。如果将此值设置为一个较大值,则新生代对象会在Survivor区进行多次复制,这样可以增加对象在新生代的存活时间, 增加在新生代即被回收的概率, 减少Full GC 的频率,这样做可以在某种程度上提高服务稳定性。该参数只有在串行 GC 时才有效,这个值的设置是根据本地的jprofiler监控后得到的一个理想的值, 不能一概而论原搬照抄;
  • -XX:+CMSParallelRemarkEnabled: 在使用 UseParNewGC 的情况下, 尽量减少mark 的时间;
  • -XX:+UseCMSCompactAtFullCollection : 在使用concurrent gc 的 情况下 ,防止memoryfragmention,对live object进行整理,使memory碎片减少;
  • -XX:LargePageSizeInBytes: 指定Java heap的分页页面大小,内存页的大小不可设置过大,会影响Perm的大小;
  • -XX:+UseFastAccessorMethods: 使用get,set方法转成本地代码, 原始类型的快速优化;
  • -XX:+UseCMSInitiatingOccupancyOnly:只有在oldgeneration在使用了初始化的比例后concurrent collector启动收集;
  • -Duser.timezone=Asia/Shanghai: 设置用户所在时区;
  • -Djava.awt.headless=true: 这个参数一般我们都是放在最后使用。 有时我们会在我们的J2EE工程中使用一些图表工具,如:jfreechart,用于在 Web 网页输出 GIF/JPG 等流,在 Windows 环境下, 一般我们的 app server 在输出图形时不会碰到什么问题, 但是在Linux/Unix 环境下经常会碰到一个 exception 导致你在 Windows开发环境下图片正常显
    示, 可是在 Linux/Unix下却显示不出来,因此加上这个参数以免避这样的情况出现;
  • -Xmn: 新生代的内存空间大小, 注意: 此处的大小是(eden+ 2 survivor space)。 与 jmap
    -heap 中显示的 New gen 是不同的。 整个堆大小 = 新生代大小 + 老生代大小 + 永久代大小。 在保证堆大小不变的情况下, 增大新生代后, 将会减小老生代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的 3/8;
  • -XX:CMSInitiatingOccupancyFraction:当堆满之后,并行收集器便开始进行垃圾收集。例如, 当没有足够的空间来容纳新分配或提升的对象。 对于 CMS 收集器,长时间等待是不可取的,因为在并发垃圾收集期间应用持续在运行(并且分配对象)。因此,为了在应用程序使用完内存之前完成垃圾收集周期, CMS 收集器要比并行收集器更先启动。因为不同的应用会有不同对象分配模式, JVM 会收集实际的对象分配(和释放) 的运行时数据, 并且分析这些数据,来决定什么时候启动一次CMS垃圾收集周期。这个参数设置有很大技巧,基本上满足(Xmx-Xmn)(100-CMSInitiatingOccupancyFraction)/100 >= Xmn 就不会出现promotion failed。 例如在应用中 Xmx 是 6000, Xmn 是 512, 那么 Xmx-Xmn 是 5488M,也就是老年代有 5488M, CMSInitiatingOccupancyFraction=90 说明老年代到 90% 满的
    时候开始执行对老年代的并发垃圾回收( CMS), 这时还 剩 10%的空间是 5488
    10% =548M, 所以即使Xmn(也就是新生代共512M)里所有对象都搬到老年代里,548M的空间也足够了,所以只要满足上面的公式,就不会出现垃圾回收时的 promotion failed,因此这个参数的设置必须与Xmn关联在一起;
  • -XX:+CMSIncrementalMode: 该标志将开启 CMS 收集器的增量模式。 增量模式经常暂停 CMS 过程, 以便对应用程序线程作出完全的让步。 因此, 收集器将花更长的时间完成整个收集周期。因此,只有通过测试后发现正常 CMS 周期对应用程序线程干扰太大时,才应该使用增量模式。由于现代服务器有足够的处理器来适应并发的垃圾收集,所以这种情况发生得很少, 用于单CPU情况。
  • -XX:NewRatio: 年轻代( 包括 Eden 和两个 Survivor 区) 与年老代的比值( 除去持久代), -XX:NewRatio=4 表示年轻代与年老代所占比值为 1:4, 年轻代占整个堆栈的 1/5,
    Xms=Xmx 并且设置了 Xmn 的情况下, 该参数不需要进行设置;
  • -XX:SurvivorRatio: Eden区与Survivor区的大小比值, 设置为8, 表示 2 个Survivor 区( JVM 堆内存年轻代中默认有 2 个大小相等的 Survivor 区) 与 1 个 Eden区的比值为 2:8, 即 1 个 Survivor 区占整个年轻代大小的 1/10;
  • -XX:+UseSerialGC: 设置串行收集器;
  • -XX:+UseParallelGC: 设置为并行收集器。此配置仅对年轻代有效。 即年轻代使用并行收集, 而年老代仍使用串行收集;
  • -XX:+UseParallelOldGC: 配置年老代垃圾收集方式为并行收集, JDK6.0 开始支持对年老代并行收集;
  • -XX:ConcGCThreads: 早期 JVM 版本也叫-XX:ParallelCMSThreads, 定义并发 CMS过程运行时的线程数。比如value=4意味着CMS周期的所有阶段都以4个线程来执行。尽管更多的线程会加快并发CMS过程,但其也会带来额外的同步开销。因此,对于特定的应用程序,应该通过测试来判断增加 CMS 线程数是否真的能够带来性能的提升。如果改标志未设置,JVM会根据并行收集器中的 -XX:ParallelGCThreads 参数的值来计算出默认的并行 CMS 线程数;
  • -XX:ParallelGCThreads:配置并行收集器的线程数,即:同时有多少个线程一起进行垃圾回收, 此值建议配置与 CPU 数目相等;
  • -XX:OldSize:设置JVM启动分配的老年代内存大小,类似于新生代内存的初始大小-XX:NewSize。

以上就是一些常用的配置参数,但是有些参数是可以被替代的,配置思路需要考虑的是Java 提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾能够接受的速度和应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。假如堆的大小很大,那么完全垃圾收集就会很慢, 但是频度会降低。假如您把堆的大小和内存的需要一致, 完全收集就很快, 但是会更加频繁。调整堆大小的目的是最小化垃圾收集的
时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为确保最好的性能,要把堆的大小设大,确保垃圾收集不在整个基准测试的过程中出现。

堆大小决定了虚拟机花费在收集垃圾上的时间和频度

Tomcat部署及优化 - 图9

上述关于JVM优化参数太多,很多参数需要对GC回收有很深刻的认识。如果优化的不合适,往往会起到事倍功半的效果。下面是常见的优化参数,修改 /usr/local/tomcat/bin/catalina.sh,增加JAVA_OPTS行。

  1. [root@www tomcat]# vim bin/catalina.sh
  2. # OS specific support. $var _must_ be set to either true or false.
  3. JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:PermSize=1024m -XX:MaxPermSize=2048m"
  4. cygwin=false

重启tomcat进行测试:

Tomcat部署及优化 - 图10

结论:在每次优化完后压测数据都可能会存在差异,甚至环境一样压测结果都会不一样, 一般都是取多组数据平均值。本案例中的简单的优化配置也不一定适合你的环境,但是至少优化的方向是正确的。如果想对jmeter进行深入学习,请查阅相关文档。

tomcat运行模式

Tomcat支持三种接收请求的处理方式:BIO、NIO、APR

  • BIO :由于每个请求都要创建一个线程来处理,线程开销比较大,不能在高并发的场景,性能也是最低的;
  • NIO :是一个基于缓冲区、并能提供非阻塞I/O操作的Java API,比传统的BIO更好的并发性能;
  • APR(Apache Portable Run-time libraries): 简单理解,就是从操作系统级别解决异步IO问题,大幅度的提高服务器的处理和响应性能,也是Tomcat运行高并发应用的首选模式。
  1. 安装apr库
  1. [root@www tomcat]# yum install -y apr-devel openssl-devel gcc make expat-devel libtool
  2. # apr需要1.2版本以上
  3. # openssl需要1.0.2+以上版本
  1. 安装tomcat-antive
  1. [root@www tomcat]# cd /usr/local/tomcat/bin/
  2. [root@www bin]# tar zxvf tomcat-native.tar.gz
  3. [root@www bin]# cd tomcat-native-1.2.23-src/native/
  4. [root@www native]# ./configure
  5. [root@www native]# make
  6. [root@www native]# make install
  7. # 默认安装在/usr/local/apr/lib
  1. 更改环境变量
  1. [root@www tomcat]# vim /etc/profile
  2. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/apr/lib
  3. [root@www tomcat]# source /etc/profile
  1. 修改tomcat的server.xml文件
  1. [root@www tomcat]# vim /usr/local/tomcat/conf/server.xml
  2. <Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
  3. connectionTimeout="20000"
  4. redirectPort="8443" />
  5. # 修改HTTP连接器
  6. <Connector port="8009" protocol="org.apache.coyote.ajp.AjpAprProtocol" redirectPort="8443" />
  7. # 修改AJP连接器
  1. 启动tomcat并查看是否成功
  1. [root@www tomcat]# /usr/local/tomcat/bin/startup.sh
  2. [root@www tomcat]# tail -5 /usr/local/tomcat/logs/catalina.2019-08-05.log
  3. 05-Aug-2019 13:16:05.443 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/bbs]
  4. 05-Aug-2019 13:16:05.460 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/bbs] has finished in [16] ms
  5. 05-Aug-2019 13:16:05.475 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-apr-8080"]
  6. 05-Aug-2019 13:16:05.503 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-apr-8009"]
  7. 05-Aug-2019 13:16:05.512 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [1,587] milliseconds

常见错误说明

1. java.lang.OutOfMemoryError: Java heap space——JVM Heap(堆)溢出

JVM 在启动的时候会自动设置 JVM Heap 的值,其初始空间(即-Xms)是物理内存的 1/64,最大空间(-Xmx)不可超过物理内存。可以利用 JVM 提供的 -Xmn -Xms -Xmx 等 选项可进行设置。Heap 的大小是 Young Generation 和 Tenured Generaion 之和。在 JVM 中如果 98%的时间是用于 GC,且可用的 Heap size 不足 2%的时候将抛出此异常信息。

解决方法:手动设置 JVM Heap(堆)的大小。

2. java.lang.OutOfMemoryError: PermGen space——PermGen space 溢出

PermGen space 的全称是 Permanent Generation space,是指内存的永久保存区域。 为什么会内存溢出,这是由于这块内存主要是被 JVM 存放 Class 和 Meta 信息的,Class 在被 Load 的时候被放入 PermGen space 区域,它和存放 Instance 的 Heap 区域不同,sun 的 GC 不会在主程序运行期对 PermGen space 进行清理。所以,如果你的 APP 会载入很 多 CLASS 的话,就很可能出现 PermGen space 溢出。

解决方法:手动设置 MaxPermSize 大小。

3. java.lang.StackOverflowError——栈溢出

JVM 依然是采用栈式的虚拟机,这个和 C 与 Pascal 都是一样的。函数的调用过程都体 现在堆栈和退栈上了。调用构造函数的“层”太多了,以致于把栈区溢出了。通常来讲,一般 栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K 的空间(这个大约相当于在一个 C 函数内声明了 256 个 int 类型的变量),那么栈区也 不过是需要 1MB 的空间。通常栈的大小是1-2MB的。通常递归也不要递归的层次过多,很容易溢出。

解决方法:修改程序,减少函数的“层”数。