Tomcat-容器实战及调优
第1章 Tomcat简介
Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun和其他一些公司及个人共同开发而成。
Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。
Tomcat和Nginx、Apache(httpd)、lighttpd等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Nginx/Apache服务器。
Java容器还有resin、weblogic等。
http://blog.csdn.net/snarlfuture/article/details/18473761
Tomcat的核心分为3个部分:
Web容器---处理静态页面;
catalina --- 一个servlet容器-----处理servlet;
JSP容器,把jsp页面编译成一般的servlet。
jar与war的区别
jar:即Java Archive,Java的包,Java编译好之后生成class文件,但如果直接发布这些class文件的话会很不方便,所以就把许多的class文件打包成一个jar,jar中除了class文件还可以包括一些资源和配置文件,通常一个jar包就是一个java程序或者一个java库。
war:Web application Archive,与jar基本相同,但它通常表示这是一个Java的Web应用程序的包,tomcat这种Servlet容器会认出war包并自动部署。
第2章 Tomcat安装与配置
2.1 软件准备
JDK下载:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Tomcat下载:http://tomcat.apache.org/
因为jdk和Tomcat的版本对运维来说没什么区别,所以生产中对版本没有太多的要求,目前Tomcat最新版本为9.0。。
2.2 部署java环境
[root@tomcat ~]# cd /application/tools/
[root@tomcat tools]# rz
tar xf jdk-8u131-linux-x64.tar.gz -C /application/
ln -s /application/jdk1.8.0_131 /application/jdk
sed -i.ori '$a export JAVA_HOME=/application/jdk\nexport PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH\nexport CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar' /etc/profile
source /etc/profile
#→出现下面结果证明部署成功
[root@web01 application]# java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
2.3 安装Tomcat
tar xf apache-tomcat-8.0.27.tar.gz -C /application/
ln -s /application/apache-tomcat-8.0.27 /application/tomcat
echo 'export TOMCAT_HOME=/application/tomcat'>>/etc/profile
source /etc/profile
chown -R root.root /application/jdk/ /application/tomcat/
[root@tomcat ~]# tail -4 /etc/profile
export JAVA_HOME=/application/jdk
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
export TOMCAT_HOME=/application/tomcat
2.4 Tomcat目录介绍
[root@tomcat ~]# cd /application/tomcat/
[root@tomcat tomcat]# tree -L 1
├── bin #→用以启动、关闭Tomcat或者其它功能的脚本(.bat文件和.sh文件)
├── conf #→用以配置Tomcat的XML及DTD文件
├── lib #→存放web应用能访问的JAR包
├── LICENSE
├── logs #→Catalina和其它Web应用程序的日志文件
├── NOTICE
├── RELEASE-NOTES
├── RUNNING.txt
├── temp #→临时文件
├── webapps #→Web应用程序根目录
└── work #→用以产生由JSP编译出的Servlet的.java和.class文件
7 directories, 4 files
[root@tomcat tomcat]# cd webapps/
[root@tomcat webapps]# ll
total 20
drwxr-xr-x 14 root root 4096 Oct 5 12:09 docs #→tomcat帮助文档
drwxr-xr-x 6 root root 4096 Oct 5 12:09 examples #→web应用实例
drwxr-xr-x 5 root root 4096 Oct 5 12:09 host-manager #→管理
drwxr-xr-x 5 root root 4096 Oct 5 12:09 manager #→管理
drwxr-xr-x 3 root root 4096 Oct 5 12:09 ROOT #→默认网站根目录
2.5 启动Tomcat
#→启动程序/application/tomcat/bin/startup.sh
#→关闭程序/application/tomcat/bin/shutdown.sh
[root@tomcat ~]# /application/tomcat/bin/startup.sh
Using CATALINA_BASE: /application/tomcat
Using CATALINA_HOME: /application/tomcat
Using CATALINA_TMPDIR: /application/tomcat/temp
Using JRE_HOME: /application/jdk
Using CLASSPATH: /application/tomcat/bin/bootstrap.jar:/application/tomcat/bin/tomcat-juli.jar
Tomcat started.
[root@tomcat ~]# netstat -tunlp|grep java
tcp 0 0 :::8009 :::* LISTEN 4743/java
tcp 0 0 :::8080 :::* LISTEN 4743/java
[root@tomcat ~]# ps -ef|grep [j]ava
root 4743 1 17 06:10 pts/0 00:00:03 /application/jdk/bin/java -Djava.util.logging.config.file=/application/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/application/tomcat/endorsed -classpath /application/tomcat/bin/bootstrap.jar:/application/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/application/tomcat -Dcatalina.home=/application/tomcat -Djava.io.tmpdir=/application/tomcat/temp org.apache.catalina.startup.Bootstrap start
2.6 访问网站
网址:http://10.0.0.8:8080/(要根据你安装主机的IP配置来访问),出现如下界面表示成功安装
2.7 Tomcat日志
[root@tomcat ~]# cd /application/tomcat/logs/
[root@tomcat logs]# ls
catalina.2016-01-26.log localhost.2016-01-26.log
catalina.out localhost_access_log.2016-01-26.txt
host-manager.2016-01-26.log manager.2016-01-26.log
#→catalina.out是tomcat实时日志,服务启动异常可以查看
[root@tomcat logs]# tailf catalina.out
26-Jan-2016 06:10:24.188 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /application/apache-tomcat-8.0.27/webapps/host-manager has finished in 50 ms
26-Jan-2016 06:10:24.195 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
26-Jan-2016 06:10:24.206 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
26-Jan-2016 06:10:24.211 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 1560 ms
2.8 Tomcat配置文件
2.8.1 Tomcat配置文件
[root@tomcat conf]# pwd
/application/tomcat/conf
[root@tomcat conf]# ll -h
total 216K
drwxr-xr-x 3 root root 4.0K Jan 26 06:10 Catalina
-rw------- 1 root root 13K Sep 28 16:19 catalina.policy
-rw------- 1 root root 7.0K Sep 28 16:19 catalina.properties
-rw------- 1 root root 1.6K Sep 28 16:19 context.xml
-rw------- 1 root root 3.4K Sep 28 16:19 logging.properties
-rw------- 1 root root 6.4K Sep 28 16:19 server.xml #→主配置文件
-rw------- 1 root root 1.8K Sep 28 16:19 tomcat-users.xml #→Tomcat管理用户配置文件
-rw------- 1 root root 1.9K Sep 28 16:19 tomcat-users.xsd
-rw------- 1 root root 164K Sep 28 16:19 web.xml
2.8.2 Tomcat管理
注意:测试功能,生产环境不要用。不安全
Tomcat管理功能用于对Tomcat自身以及部署在Tomcat上的应用进行管理的web应用。在默认情况下是处于禁用状态的。如果需要开启这个功能,就需要配置管理用户,即配置前面说过的tomcat-users.xml。
[root@tomcat ~]# vim /application/tomcat/conf/tomcat-users.xml
…………
<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="tomcat" password="tomcat" roles="manager-gui,admin-gui"/>
</tomcat-users> #→在此行前加入上面三行
[root@tomcat ~]# /application/tomcat/bin/shutdown.sh
[root@tomcat ~]# /application/tomcat/bin/startup.sh
配置后就可以在web界面中管理tomcat了
2.8.3 Tomcat主配置文件Server.xml详解
配置文件注释
<?xml version='1.0' encoding='utf-8'?>
<!--
<Server>元素代表整个容器,是Tomcat实例的顶层元素.由org.apache.catalina.Server接口来定义.它包含一个<Service>元素.并且它不能做为任何元素的子元素.
port指定Tomcat监听shutdown命令端口.终止服务器运行时,必须在Tomcat服务器所在的机器上发出shutdown命令.该属性是必须的.
shutdown指定终止Tomcat服务器运行时,发给Tomcat服务器的shutdown监听端口的字符串.该属性必须设置
-->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!--service服务组件-->
<Service name="Catalina">
<!--
connector:接收用户请求,类似于httpd的listen配置监听端口.
port指定服务器端要创建的端口号,并在这个端口监听来自客户端的请求。
address:指定连接器监听的地址,默认为所有地址(即0.0.0.0)
protocol连接器使用的协议,支持HTTP和AJP。AJP(Apache Jserv Protocol)专用于tomcat与apache建立通信的, 在httpd反向代理用户请求至tomcat时使用(可见Nginx反向代理时不可用AJP协议)。
minProcessors服务器启动时创建的处理请求的线程数
maxProcessors最大可以创建的处理请求的线程数
enableLookups如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址
redirectPort指定服务器正在处理http请求时收到了一个SSL传输请求后重定向的端口号
acceptCount指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理
connectionTimeout指定超时的时间数(以毫秒为单位)
-->
<Connector port="8080" protocol="HTTP/1.1" #与Nginx反向代理建立连接的配置
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />#在与Apache建立通信的时候才使用
<!--engine,核心容器组件,catalina引擎,负责通过connector接收用户请求,并处理请求,将请求转至对应的虚拟主机host
defaultHost指定缺省的处理请求的主机名,它至少与其中的一个host元素的name属性值是一样的
-->
<Engine name="Catalina" defaultHost="localhost">
<!--Realm表示存放用户名,密码及role的数据库-->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<!--
host表示一个虚拟主机
name指定主机名
appBase应用程序基本目录,即存放应用程序的目录.一般为appBase="webapps" ,相对于CATALINA_HOME而言的,也可以写绝对路径。
unpackWARs如果为true,则tomcat会自动将WAR文件解压,否则不解压,直接从WAR文件中运行应用程序
autoDeploy:在tomcat启动时,是否自动部署。
xmlValidation:是否启动xml的校验功能,一般xmlValidation="false"。
xmlNamespaceAware:检测名称空间,一般xmlNamespaceAware="false"。
-->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!--
Context表示一个web应用程序,通常为WAR文件
path表示此web应用程序的url的前缀,这样请求的url为http://localhost:8080/path/****
docBase应用程序的路径或者是WAR文件存放的路径,也可以使用相对路径,起始路径为此Context所属Host中appBase定义的路径。
reloadable这个属性非常重要,如果为true,则tomcat会自动检测应用程序的/WEB-INF/lib 和/WEB-INF/classes目录的变化,自动装载新的应用程序,可以在不重启tomcat的情况下改变应用程序
-->
<Context path="" docBase="/application/tomcat/webapps/memtest" debug="0" reloadable="false" crossContext="true"/>
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
第3章 Tomcat使用
3.1 WEB站点部署
上线的代码有两种方式,第一种方式是直接将程序目录放在webapps目录下面,这种方式大家已经明白了,就不多说了。第二种方式是使用开发工具将程序打包成war包,然后上传到webapps目录下面。下面让我们见识一下这种方式。
3.1.1 使用war包部署web站点
[root@tomcat webapps]# pwd
/application/tomcat/webapps
[root@tomcat webapps]# rz #→上传memtest.war
#由于启动了自动部署,war文件会自动解压
[root@tomcat webapps]# ls
docs examples host-manager manager memtest memtest.war ROOT
浏览器访问:http://10.0.0.8:8080/memtest/meminfo.jsp
JVM memory detail info :
Max memory:121MB
Total memory:38MB
Free memory:14MB Available
memory can be used is :97MB
3.1.2 自定义默认网站目录
上面访问的网址为http://10.0.0.8:8080/memtest/meminfo.jsp
现在我想访问格式为http://10.0.0.8:8080/meminfo.jsp
怎么破?
方法一:将meminfo.jsp或其他程序放在tomcat/webapps/ROOT目录下即可。因为默认网站根目录为tomcat/webapps/ROOT
方法二:修改配置文件
[root@tomcat ~]# vim /application/tomcat/conf/server.xml
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="" docBase="/application/tomcat/webapps/memtest" debug="0" reloadable="false" crossContext="true"/>
[root@tomcat ~]# /application/tomcat/bin/shutdown.sh
[root@tomcat ~]# /application/tomcat/bin/startup.sh
3.2 Tomcat多实例
3.2.1 复制Tomcat目录
cd /application/
cp -a apache-tomcat-8.0.27 tomcat8_1
cp -a apache-tomcat-8.0.27 tomcat8_2
3.2.2 修改配置文件
mkdir -p /data/www/www/ROOT
cp /application/tomcat/webapps/memtest/meminfo.jsp /data/www/www/ROOT/
sed -i '22s#8005#8011#;69s#8080#8081#' /application/tomcat8_1/conf/server.xml
sed -i '22s#8005#8012#;69s#8080#8082#' /application/tomcat8_2/conf/server.xml
diff /application/tomcat/conf/server.xml /application/tomcat8_1/conf/server.xml
diff /application/tomcat/conf/server.xml /application/tomcat8_2/conf/server.xml
3.2.3 启动多实例
for i in {1..2};do /application/tomcat8_$i/bin/startup.sh;done
for i in {1..2};do /application/tomcat8_$i/bin/shutdown.sh;done
netstat -tunlp|grep java
浏览器可以分别访问http://10.0.0.8:8081和http://10.0.0.8:8082
3.3 Tomcat集群
使用nginx+Tomcat反向代理集群
[root@lb01 ~]# vim /application/nginx/conf/nginx.conf
upstream tomcat {
server 172.16.1.8:8080 weight=1;
server 172.16.1.8:8081 weight=1;
}
server {
listen 80;
server_name tomcat.panda.tv;
location / {
proxy_pass http://tomcat;
}
浏览器可以访问http://10.0.0.8/meminfo.jsp
可以测试是否是均衡状态
echo "tomcat8080" >>/application/tomcat/webapps/ROOT/index.jsp
echo "tomcat8081" >>/application/tomcat8_1/webapps/ROOT/index.jsp
3.4 Tomcat监控
企业案例:Linux下java/http进程高解决案例
生产环境下某台tomcat7服务器,在刚发布时的时候一切都很正常,在运行一段时间后就出现CPU占用很高的问题,基本上是负载一天比一天高。请搞定!
使用脚本查看tomcat负载高的线程以及警告信息,将结果反馈给开发。
[root@web01 webapps]# sh show-busy-java-threads.sh
Busy(0.2%) thread(53032/0xcf28) stack of java process(53026) under user(root):
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007ff8100ae000 nid=0xcf28 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Busy(0.1%) thread(53073/0xcf51) stack of java process(53026) under user(root):
"http-nio-8081-exec-1" #19 daemon prio=5 os_prio=0 tid=0x00007ff7fc041000 nid=0xcf51 waiting on condition [0x00007ff814bf9000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000fb949750> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
3.4.1 jconsole和jvisualvm
tomcat配置
vim /application/tomcat/bin/catalina.sh
#在98行插入
CATALINA_OPTS="$CATALINA_OPTS
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=50013
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=10.0.0.8"
需要解析主机名否则报错
[root@web01 webapps]# echo "10.0.0.8 web01" >>/etc/hosts
重启tomcat
Windows下首先安装jdk window版本
在安装目录(如笔者的安装目录为C:\Program Files\Java\jdk1.8.0_31\bin)下可以找到jconsole.exe和jvisualvm.exe文件
jconsole.exe连接方法如下所示,注意端口是在tomcat服务端配置的123456(jvisualvm连接也很简单不做演示)
第4章 Tomcat安全优化和性能优化
4.1 安全优化
1、降权启动,如果Tomcat存在漏洞,直接使用root用户运行会影响到系统安全,这个时候就可以降权运行tomcat
useradd tomcat
cp -a /application/apache-tomcat-8.0.27 /home/tomcat/tomcat
chown -R tomcat.tomcat /home/tomcat/tomcat
su - tomcat
/home/tomcat/tomcat/bin/startup.sh
[tomcat@web01 ~]$ ps -ef|grep java|grep -v grep
tomcat 54131 1 4 18:30 pts/1 00:00:03 /application/jdk/bin/java -Djava.util.logging.config.file=/home/tomcat/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=12345 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=10.0.0.8 -Djava.endorsed.dirs=/home/tomcat/tomcat/endorsed -classpath /home/tomcat/tomcat/bin/bootstrap.jar:/home/tomcat/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/home/tomcat/tomcat -Dcatalina.home=/home/tomcat/tomcat -Djava.io.tmpdir=/home/tomcat/tomcat/temp org.apache.catalina.startup.Bootstrap start
2、telnet管理端口保护 更改监听SHUTDOWN的端口
3、ajp连接端口保护 ajp用于tomcat与Apache的连接,如果使用Nginx,就可以将其禁用掉
4、禁用管理端
mv /application/tomcat/webapps/{host-manager,manager} /tmp
4.2 性能优化
4.2.1 屏蔽dns查询
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="6000" enableLookups="false" acceptCount="800"
redirectPort="8443" />
4.2.2 jvm调优*****
jvm调优最重要,Tomcat最吃内存,只要内存足够,这只猫就跑的很快。
如果系统资源有限,那就需要进行调优,提高资源使用率。
优化tomcat/bin/catalina.sh配置文件。在catalina.sh或者在startup.sh配置文件中添加以下代码(以下为参考值,实际调优还需要根据生产环境决定):
export CATALINA_BASE="/home/admin/project/java_project/jzt-mdsl-quartz"
export JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom
-DappName=jzt-mdsl-quartz
-server -Xmx4g -Xms4g
-XX:PermSize=3048m
-XX:MaxPermSize=3048m
-Xss1200k
-XX:+UseG1GC
-XX:GCTimeRatio=19
-XX:+DisableExplicitGC
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+UseFastAccessorMethods
-XX:+UseCompressedOops
-XX:G1HeapRegionSize=4m
-XX:MaxGCPauseMillis=900
-XX:+ParallelRefProcEnabled
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=8
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=$CATALINA_BASE/logs
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-Xloggc:$CATALINA_BASE/logs/GClog.log"
server:一定要作为第一个参数,在多个CPU时性能佳
-Xms:初始堆内存Heap大小,使用的最小内存,cpu性能高时此值应设的大一些
-Xmx:初始堆内存Heap最大值,使用的最大内存
上面两个值是分配JVM的最小和最大内存,取决于硬件物理内存的大小,建议均设为物理内存的一半。
-XX:PermSize:设定内存的永久保存区域,默认是物理内存的1/64。那么,如果是物理内存4GB,那么64分之一就是64MB。-XX:MaxPermSize:设定最大内存的永久保存区域;默认四分之一,如果内存4GB,那么四分之一是1024MB。在数据量的很大的文件导出时,一定要把这两个值设置上,否则会出现内存溢出的错误。
-Xss 1200 这使得JBoss每增加一个线程(thread)就会立即消耗15M内存,而最佳值应该是128K,默认值好像是512k.
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=$CATALINA_BASE/logs 内存溢出报错日志
-Xloggc:gc.log 指定垃圾收集日志文件
生产中利用zabbix监控来调整以上各项参数
tomcat最大的问题是:如果开发写的代码不够优化,经常造成内存溢出的问题
如果碰到内存溢出问题,可以重启tomcat让内存得到暂时的释放,彻底解决的问题是让开发改代码或者是增大物理内存。
Tomcat 本身优化
Tomcat 的自身参数的优化,这块很像 ApacheHttp Server。修改一下 xml 配置文件中的参数,调整最大连接数,超时等。此外,我们安装 Tomcat 是,优化就已经开始了。
1、工作方式选择
为了提升性能,首先就要对代码进行动静分离,让 Tomcat 只负责 jsp 文件的解析工作。如采用 Apache 和 Tomcat 的整合方式,他们之间的连接方案有三种选择,JK、http_proxy 和 ajp_proxy。相对于 JK 的连接方式,后两种在配置上比较简单的,灵活性方面也一点都不逊色。但就稳定性而言不像JK 这样久经考验,所以建议采用 JK 的连接方式。
2、Connector 连接器的配置
之前文件介绍过的 Tomcat 连接器的三种方式: bio、nio 和 apr,三种方式性能差别很大,apr 的性能最优, bio 的性能最差。而 Tomcat 7 使用的 Connector 默认就启用的 Apr 协议,但需要系统安装 Apr 库,否则就会使用 bio 方式。
3、配置文件优化
配置文件优化其实就是对 server.xml 优化,可以提大大提高 Tomcat 的处理请求的能力,下面我们来看 Tomcat 容器内的优化。
默认配置下,Tomcat 会为每个连接器创建一个绑定的线程池(最大线程数 200),服务启动时,默认创建了 5 个空闲线程随时等待用户请求。
首先,打开 ${TOMCAT_HOME}/conf/server.xml,搜索【<Executor name="tomcatThreadPool"】,开启并调整为
1
2
|
< Executor name = "tomcatThreadPool" namePrefix = "catalina-exec-" maxThreads = "500" minSpareThreads = "20" maxSpareThreads = "50" maxIdleTime = "60000" /> |
注意, Tomcat 7 在开启线程池前,一定要安装好 Apr 库,并可以启用,否则会有错误报出,shutdown.sh 脚本无法关闭进程。
然后,修改<Connector …>节点,增加 executor 属性,搜索【port="8080"】,调整为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
< Connector executor = "tomcatThreadPool" port = "8080" protocol = "HTTP/1.1" URIEncoding = "UTF-8" connectionTimeout = "30000" enableLookups = "false" disableUploadTimeout = "false" connectionUploadTimeout = "150000" acceptCount = "300" keepAliveTimeout = "120000" maxKeepAliveRequests = "1" compression = "on" compressionMinSize = "2048" compressableMimeType = "text/html,text/xml,text/javascript,text/css,text/plain,image/gif,image/jpg,image/png" redirectPort = "8443" /> |
maxThreads :Tomcat 使用线程来处理接收的每个请求,这个值表示 Tomcat 可创建的最大的线程数,默认值是 200
minSpareThreads:最小空闲线程数,Tomcat 启动时的初始化的线程数,表示即使没有人使用也开这么多空线程等待,默认值是 10。
maxSpareThreads:最大备用线程数,一旦创建的线程超过这个值,Tomcat 就会关闭不再需要的 socket 线程。
上边配置的参数,最大线程 500(一般服务器足以),要根据自己的实际情况合理设置,设置越大会耗费内存和 CPU,因为 CPU 疲于线程上下文切换,没有精力提供请求服务了,最小空闲线程数 20,线程最大空闲时间 60 秒,当然允许的最大线程连接数还受制于操作系统的内核参数设置,设置多大要根据自己的需求与环境。当然线程可以配置在“tomcatThreadPool”中,也可以直接配置在“Connector”中,但不可以重复配置。
URIEncoding:指定 Tomcat 容器的 URL 编码格式,语言编码格式这块倒不如其它 WEB 服务器软件配置方便,需要分别指定。
connnectionTimeout: 网络连接超时,单位:毫秒,设置为 0 表示永不超时,这样设置有隐患的。通常可设置为 30000 毫秒,可根据检测实际情况,适当修改。
enableLookups: 是否反查域名,以返回远程主机的主机名,取值为:true 或 false,如果设置为false,则直接返回IP地址,为了提高处理能力,应设置为 false。
disableUploadTimeout:上传时是否使用超时机制。
connectionUploadTimeout:上传超时时间,毕竟文件上传可能需要消耗更多的时间,这个根据你自己的业务需要自己调,以使Servlet有较长的时间来完成它的执行,需要与上一个参数一起配合使用才会生效。
acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可传入连接请求的最大队列长度,超过这个数的请求将不予处理,默认为100个。
keepAliveTimeout:长连接最大保持时间(毫秒),表示在下次请求过来之前,Tomcat 保持该连接多久,默认是使用 connectionTimeout 时间,-1 为不限制超时。
maxKeepAliveRequests:表示在服务器关闭之前,该连接最大支持的请求数。超过该请求数的连接也将被关闭,1表示禁用,-1表示不限制个数,默认100个,一般设置在100~200之间。
compression:是否对响应的数据进行 GZIP 压缩,off:表示禁止压缩;on:表示允许压缩(文本将被压缩)、force:表示所有情况下都进行压缩,默认值为off,压缩数据后可以有效的减少页面的大小,一般可以减小1/3左右,节省带宽。
compressionMinSize:表示压缩响应的最小值,只有当响应报文大小大于这个值的时候才会对报文进行压缩,如果开启了压缩功能,默认值就是2048。
compressableMimeType:压缩类型,指定对哪些类型的文件进行数据压缩。
noCompressionUserAgents="gozilla, traviata": 对于以下的浏览器,不启用压缩。
如果已经对代码进行了动静分离,静态页面和图片等数据就不需要 Tomcat 处理了,那么也就不需要配置在 Tomcat 中配置压缩了。
以上是一些常用的配置参数属性,当然还有好多其它的参数设置,还可以继续深入的优化,HTTP Connector 与 AJP Connector 的参数属性值,可以参考官方文档的详细说明:
https://tomcat.apache.org/tomcat-7.0-doc/config/http.html
https://tomcat.apache.org/tomcat-7.0-doc/config/ajp.html
JVM 优化
Tomcat 启动命令行中的优化参数,就是 JVM 的优化 。Tomcat 首先跑在 JVM 之上的,因为它的启动其实也只是一个 java 命令行,首先我们需要对这个 JAVA 的启动命令行进行调优。不管是 YGC 还是 Full GC,GC 过程中都会对导致程序运行中中断,正确的选择不同的 GC 策略,调整 JVM、GC 的参数,可以极大的减少由于 GC 工作,而导致的程序运行中断方面的问题,进而适当的提高 Java 程序的工作效率。但是调整 GC 是以个极为复杂的过程,由于各个程序具备不同的特点,如:web 和 GUI 程序就有很大区别(Web可以适当的停顿,但GUI停顿是客户无法接受的),而且由于跑在各个机器上的配置不同(主要 cup 个数,内存不同),所以使用的 GC 种类也会不同。
1、JVM 参数配置方法
Tomcat 的启动参数位于安装目录 ${JAVA_HOME}/bin目录下,Linux 操作系统就是 catalina.sh 文件。JAVA_OPTS,就是用来设置 JVM 相关运行参数的变量,还可以在 CATALINA_OPTS 变量中设置。关于这 2 个变量,还是多少有些区别的:
JAVA_OPTS:用于当 Java 运行时选项“start”、“stop”或“run”命令执行。
CATALINA_OPTS:用于当 Java 运行时选项“start”或“run”命令执行。
为什么有两个不同的变量?它们之间都有什么区别呢?
首先,在启动 Tomcat 时,任何指定变量的传递方式都是相同的,可以传递到执行“start”或“run”命令中,但只有设定在 JAVA_OPTS 变量里的参数被传递到“stop”命令中。对于 Tomcat 运行过程,可能没什么区别,影响的是结束程序,而不是启动程序。
第二个区别是更微妙,其他应用程序也可以使用 JAVA_OPTS 变量,但只有在 Tomcat 中使用 CATALINA_OPTS 变量。如果你设置环境变量为只使用 Tomcat,最好你会建议使用 CATALINA_OPTS 变量,而如果你设置环境变量使用其它的 Java 应用程序,例如 JBoss,你应该把你的设置放在JAVA_OPTS 变量中。
2、JVM 参数属性
32 位系统下 JVM 对内存的限制:不能突破 2GB ,那么这时你的 Tomcat 要优化,就要讲究点技巧了,而在 64 位操作系统上无论是系统内存还是 JVM 都没有受到 2GB 这样的限制。
针对于 JMX 远程监控也是在这里设置,以下为 64 位系统环境下的配置,内存加入的参数如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
CATALINA_OPTS=" -server -Xms6000M -Xmx6000M -Xss512k -XX:NewSize=2250M -XX:MaxNewSize=2250M -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+AggressiveOpts -XX:+UseBiasedLocking -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=31 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -Duser.timezone=Asia /Shanghai -Djava.awt.headless= true " |
为了看着方便,将每个参数单独写一行。上面参数好多啊,可能有人写到现在都没见过一个在 Tomcat 的启动命令里加了这么多参数,当然,这些参数只是我机器上的,不一定适合你,尤其是参数后的 value(值)是需要根据你自己的实际情况来设置的。
上述这样的配置,基本上可以达到:
系统响应时间增快;
JVM回收速度增快同时又不影响系统的响应率;
JVM内存最大化利用;
线程阻塞情况最小化。
JVM 常用参数详解:
-server:一定要作为第一个参数,在多个 CPU 时性能佳,还有一种叫 -client 的模式,特点是启动速度比较快,但运行时性能和内存管理效率不高,通常用于客户端应用程序或开发调试,在 32 位环境下直接运行 Java 程序默认启用该模式。Server 模式的特点是启动速度比较慢,但运行时性能和内存管理效率很高,适用于生产环境,在具有 64 位能力的 JDK 环境下默认启用该模式,可以不配置该参数。
-Xms:表示 Java 初始化堆的大小,-Xms 与-Xmx 设成一样的值,避免 JVM 反复重新申请内存,导致性能大起大落,默认值为物理内存的 1/64,默认(MinHeapFreeRatio参数可以调整)空余堆内存小于 40% 时,JVM 就会增大堆直到 -Xmx 的最大限制。
-Xmx:表示最大 Java 堆大小,当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃,因此一般建议堆的最大值设置为可用内存的最大值的80%。如何知道我的 JVM 能够使用最大值,使用 java -Xmx512M -version 命令来进行测试,然后逐渐的增大 512 的值,如果执行正常就表示指定的内存大小可用,否则会打印错误信息,默认值为物理内存的 1/4,默认(MinHeapFreeRatio参数可以调整)空余堆内存大于 70% 时,JVM 会减少堆直到-Xms 的最小限制。
-Xss:表示每个 Java 线程堆栈大小,JDK 5.0 以后每个线程堆栈大小为 1M,以前每个线程堆栈大小为 256K。根据应用的线程所需内存大小进行调整,在相同物理内存下,减小这个值能生成更多的线程,但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。一般小的应用, 如果栈不是很深, 应该是128k 够用的,大的应用建议使用 256k 或 512K,一般不易设置超过 1M,要不然容易出现out ofmemory。这个选项对性能影响比较大,需要严格的测试。
-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:+UseConcMarkSweepGC:设置年老代为并发收集,即 CMS gc,这一特性只有 jdk1.5
后续版本才具有的功能,它使用的是 gc 估算触发和 heap 占用触发。我们知道频频繁的 GC 会造面 JVM
的大起大落从而影响到系统的效率,因此使用了 CMS GC 后可以在 GC 次数增多的情况下,每次 GC 的响应时间却很短,比如说使用了 CMS
GC 后经过 jprofiler 的观察,GC 被触发次数非常多,而每次 GC 耗时仅为几毫秒。
-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 等流,在 winodws 环境下,一般我们的 app server 在输出图形时不会碰到什么问题,但是在linux/unix 环境下经常会碰到一个 exception 导致你在 winodws 开发环境下图片显示的好好可是在 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 提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾能够接受的速度和应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。假如堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。假如您把堆的大小和内存的需要一致,完全收集就很快,但是会更加频繁。调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为确保最好的性能,要把堆的大小设大,确保垃圾收集不在整个基准测试的过程中出现。
假如系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过 3-5 秒。假如垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的周详输出,研究垃圾收集参数对性能的影响。当增加处理器时,记得增加内存,因为分配能够并行进行,而垃圾收集不是并行的。
3、设置系统属性
之前说过,Tomcat 的语言编码,配置起来很慢,要经过多次设置才可以了,否则中文很有可能出现乱码情况。譬如汉字“中”,以 UTF-8 编码后得到的是 3 字节的值 %E4%B8%AD,然后通过 GET 或者 POST 方式把这 3 个字节提交到 Tomcat 容器,如果你不告诉 Tomcat 我的参数是用 UTF-8编码的,那么 Tomcat 就认为你是用 ISO-8859-1 来编码的,而 ISO8859-1(兼容 URI 中的标准字符集 US-ASCII)是兼容 ASCII 的单字节编码并且使用了单字节内的所有空间,因此 Tomcat 就以为你传递的用 ISO-8859-1 字符集编码过的 3 个字符,然后它就用 ISO-8859-1 来解码。
设置起来不难使用“ -D<名称>=<值> ”来设置系统属性:
-Djavax.servlet.request.encoding=UTF-8
-Djavax.servlet.response.encoding=UTF-8
-Dfile.encoding=UTF-8
-Duser.country=CN
-Duser.language=zh
4、常见的 Java 内存溢出有以下三种
(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 的。
通常递归也不要递归的层次过多,很容易溢出。