一、Tomcat 简介
1、Java虚拟机
所谓虚拟机,就是一台虚拟的计算机。他是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。大名鼎鼎的VisualBox、VMware就属于系统虚拟机。他们完全是对物理计算机的仿真。提供了一个可以运行完整操作系统的软件平台。
程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令。无论是系统虚拟机还是程序虚拟机,在上面运行的软件都呗限制于虚拟机提供的资源中。
JAVA 如何做到跨平台
同一个JAVA程序(JAVA字节码的集合),通过JAVA虚拟机(JVM)运行于各大主流操作系统平台比如Windows、CentOS、Ubuntu等。程序以虚拟机为中介,来实现跨平台。
2、Tomcat、Resin、JBOSS、WebSphere(IBM)、Weblogic(Oracle)Web容器
早期的 Web 应用主要用于浏览新闻等静态页面,HTTP服务器(比如 Apache、Nginx)向浏览器返回静态 HTML,浏览器负责解析 HTML,将结果呈现给用户。
随着互联网的发展,我们已经不满足于仅仅浏览静态页面,还希望通过一些交互操作,来获取动态结果,因此也就需要一些扩展机制能够让 HTTP 服务器调用 服务端程序。例如 Sun 公司推出了 Servlet 技术,可以把 Servlet 简单理解为运行在服务端的 Java 小程序,但是 Servlet 没有 main 方法,不能独立运行,因此必须把它部署到 Servlet 容器中,由容器来实例化并调用 Servlet。
而 Tomcat 就是一个 Servlet 容器。为了方便使用,它们也具有 HTTP服务器的功能,因此Tomcat 就是一个“HTTP 服务器 + Servlet 容器”,我们也叫它们Web容器。
HTTP 服务器要解析处理HTTP 协议,它底层的通信协议(传输层的协议)是TCP/IP 协议。(也能说基于TCP/IP 构建了HTTP 协议)。
Web容器选型
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,Tomcat 5支持最新的Servlet 2.4 和JSP 2.0 规范。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。
Resin是CAUCHO公司的产品,是一个非常流行的支持servlets和jsp的引擎,速度非常快。Resin本身包含了一个支持HTTP/1.1的WEB服务器。它不仅可以显示动态内容,而且它显示静态内容的能力也非常强,速度直逼APACHESERVER。许多站点都是使用该WEB服务器构建的
JBoss是一个基于J2EE的开放源代码的应用服务器。 JBoss代码遵循LGPL许可,可以在任何商业应用中免费使用。JBoss是一个管理EJB的容器和服务器,支持EJB 1.1、EJB 2.0和EJB3的规范。但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。
WebSphere 是 IBM 的软件平台。它包含了编写、运行和监视全天候的工业强度的随需应变 Web 应用程序和跨平台、跨产品解决方案所需要的整个中间件基础设施,如服务器、服务和工具。WebSphere 提供了可靠、灵活和健壮的软件。
WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
二、Tomcat 安装部署
1、Tomcat安装
tomcat下载地址: Apache Tomcat® - Welcome!
JDK下载地址:Java Downloads | Oracle
1)JDK
JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。
JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。
2)JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
3)JRE
JRE为Java Runtime Environment运行环境的简称,Java Runtime Environment(包括Java Plug-in)是Sun的产品,包括两部分:Java Runtime Environment和Java Plug-in
JavaRuntimeEnvironment(JRE)是可以在其上运行、测试和传输应用程序的Java平台。
环境要求:6到8G内存。
建议卸载默认安装openjdk软件。
① 部署JAVA环境
1. 解压安装包
tar xf jdk-8u151-linux-x64.tar.gz -C /usr/local
2. 多版本部署java
ln -s /usr/local/jdk1.8.0_151/ /usr/local/java
3. 配置环境变量
vim /etc/profile
JAVA_HOME=/usr/local/java
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME PATH
source /etc/profile
env |grep JAVA
JAVA_HOME=/usr/local/java
4. 测试java
java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
② 安装Tomcat
1. 解压安装包
# tar xf apache-tomcat-7.0.42.tar.gz -C /usr/local/
2. 多版本部署tomcat
# ln -s /usr/local/apache-tomcat-7.0.42/ /usr/local/tomcat
3. 定义tomcat所需环境变量
vim /etc/profile
CATALINA_HOME=/usr/local/tomcat //Tomcat安装目录
export CATALINA_HOME
source /etc/profile
4. 启动tomcat
启动tomcat
bash /usr/local/tomcat/bin/startup.sh
输出提示
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr/local/java
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
检查端口
netstat -tnlp |grep java
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 56119/java
tcp6 0 0 :::8009 :::* LISTEN 56119/java
tcp6 0 0 :::8080 :::* LISTEN 56119/java
//关于tomcat端口:
//8005:是tomcat本身的端口
//8080:tomcat负责建立HTTP连接。在通过浏览器访问Tomcat服务器的Web应用时,使用的就是这个连接器
//8009:tomcat负责和其他的HTTP服务器建立连接,如nginx和apache互通时使用
访问服务器地址,注意关闭防火墙
http://192.168.19.100:8080/
5. 关闭tomcat
bash /usr/local/tomcat/bin/shutdown.sh
2、Tomcat部署网站
① 安装MySQL
创建jspgou数据库,字符集为utf-8:
yum install -y mariadb-server mariadb
//该步骤出错。请您检查YUM源配置
//wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
//wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
systemctl start mariadb
mysqladmin -u root password 123
//如果有密码,mysqladmin -u root -p老密码 password 新密码
mysql -u root -p123
MariaDB [(none)]> create database jspgou character set = utf8;
② 部署Web站点
1. 解压源码包
unzip jspgouV6-ROOT.zip
2. 更改数据库链接
//提示:在解压缩后的文件中,修改连接数据库的信息
vim ROOT/WEB-INF/config/jdbc.properties
jdbc.url=jdbc:mysql://127.0.0.1:3306/jspgou?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123
3. 导入数据
mysql -u root -p123 -D jspgou < DB/jspgou.sql
//使用mysql作为数据库时,如果导入数据失败
//1.修改mysql配置文件: my.cnf中max_allowed_packet参数为64m,默认为1m
//2.DB/jspgou.sql里面的默认值改一下:把所有datetime类型的字段默认值改成CURRENT_TIMESTAMP
将程序解压后的ROOT文件夹,拷贝到tomcat安装目录下的webapps文件夹下
\cp -r ROOT /usr/local/tomcat/webapps/
不使用cp 的别名。 alias cp=‘cp -i’
4. 部署网站
启动tomcat
bash /usr/local/tomcat/bin/startup.sh
输入以下地址
http://192.168.0.104:8080/jeeadmin/jspgou/index.do
注意服务器地址
用户名:admin
密 码:123456
5. 启动脚本
# vim /etc/init.d/tomcat
#!/bin/bash
# Init file for Tomcat server daemon
#
# chkconfig: 2345 96 14
# description: Tomcat server daemon
JAVA_OPTS='-Xms64m -Xmx128m'
JAVA_HOME=/usr/java/jdk1.7.0_11/
CATALINA_HOME=/usr/local/tomcat
export JAVA_OPTS JAVA_HOME CATALINA_HOME
exec $CATALINA_HOME/bin/catalina.sh $*
6. 添加到chkconfig管理
# chmod a+x /etc/init.d/tomcat
# chkconfig --add tomcat
# chkconfig tomcat --list
tomcat 0:关闭 1:关闭 2:启用 3:启用 4:启用 5:启用 6:关闭
# chkconfig tomcat on
#
# service tomcat stop
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr/java/jdk1.7.0_11
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
# service tomcat start
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr/java/jdk1.7.0_11
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
三、Tomcat 总体架构
1、Tomcat 和 HTTP 协议关联
HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP是基于TCP/IP协议来传递数据的(HTML文件、图片、查询结果等),HTTP 协 议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式(HTTP是格式规范,TCP/IP是实现)。
请你来看下面这张图,我们过一遍一次 HTTP 的请求过程:
从图上你可以看到,这个过程是:
1. 用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览器获取了这个事件。
2. 浏览器向服务端发出 TCP 连接请求。
3. 服务程序接受浏览器的连接请求,并经过 TCP 三次握手建立连接。
4. 浏览器将请求数据打包成一个 HTTP 协议格式的数据包。
5. 浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。
6. 服务端程序拿到这个数据包后,同样以 HTTP 协议格式解包,获取到客户端的意图。
7. 得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。
8. 服务器将响应结果(可能是 HTML 或者图片等)按照 HTTP 协议格式打包。
9. 服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。
10. 浏览器拿到数据包后,以 HTTP 协议的格式解包,然后解析数据,假设这里的数据是 HTML。
11. 浏览器将 HTML 文件展示在页面上。那我们想要探究的 Tomcat 和 Jetty 作为一个 HTTP 服务器,在这个过程中都做了些什么事情呢?主要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。这里请你注意,可能有成千上万的浏览器同时请求同一个 HTTP 服务器,因此 Tomcat 和 Jetty 为了提高服务的能力和并发度,往往会将自己要做的几个事情并行化,具体来说就是使用多线程的技术。
浏览器需要从远程HTTP服务器获取一个HTML文本,在这个过程中,浏览器实际做有两件事:
1. 与服务器建立Socket连接。
2. 生成请求数据并通过Socket发送出去。
用户在登陆页面输入用户名和密码,点击登陆后,浏览器发出了这样的HTTP请求:
HTTP 请求数据由三部分组成,分别是请求行、请求报头、请求正文。当这个 HTTP 请求数据到达 Tomcat 后,Tomcat 会把 HTTP 请求数据字节流解析成一个 Request 对象,这个 Request 对象封装了 HTTP 所有的请求信息。接着 Tomcat 把这个 Request 对象交给 Web 应用去处理,处理完后得到一个 Response 对象,Tomcat 会把这个 Response 对象转成 HTTP 格式的响应数据并发送给浏览器。
Response请求:
再来看看 HTTP 响应的格式,HTTP 的响应也是由三部分组成,分别是状态行、响应报头、报文主体。
在传输层协议之上,Tomcat 拿到请求数据之后进行封装,封装成HTTP 协议,这算是Tomcat 的一块核心。
Tomcat 的HTTP 服务器的功能:首先把请求的HTTP 协议解析出来,解析完之后,要进行一个业务的处理,所以就将数据交给了Servlet 容器来做。这里面就定义了很多Litener、Filter 等这些Servlet 规范(Servlet 接口),通过这些接口我们将数据传到我们的业务代码中。
从Connector 获取到连接,从连接当中获取请求数据,将请求数据解析成我们的HTTP 协议。HTTP 协议解析完成以后,将数据交给Servlet 容器。通过Servlet 接口的方法,我们对Serlvet 接口方法进行实现,就能进行相关的Servlet 业务处理。
从Connector 传到Engine 中的Host(根据请求带的不同域名,来到不同Host) 的Context(就是我们的Web 项目), Context 的Wrapper(这是Tomcat 对Servlet 的一个封装,每一个Servlet 对应一个Wrapper),这个过程叫管道线模型。
SpringBoot 会为每个Web 项目引入一个内嵌的Tomcat,即一个Web 项目对应一个Tomcat。
一般后端项目都分为两部分:网络层,逻辑业务层。
用目前最稳定的协议TCP/IP 协议负责网络层的交互(能进行网络互连),网络层把数据交给业务层。
那么这个逻辑反映到Tomcat 的架构中就是网络层直接的数据传输用Connector 进行处理,业务就交给Context 处理。那么数据要从Connector传到Context才能进行业务处理。包括因为访问路径的域名不同,对应不同的Host 组件进行处理,Host 中又有不同的Context(工程) 来完成不同的任务。
2、Servlet 规范
HTTP服务器怎么知道要调用哪个Java类的哪个方法呢。最直接的做法是在HTTP服务器代码里写一大堆ifelse逻辑判断:如果是A请求就调X类的M1方法,如果是B请求就调Y类的M2方法。但这样做明显有问题,因为HTTP服务器的代码跟业务逻辑耦合在一起了,如果新加一个业务方法还要改HTTP服务器的代码。
那该怎么解决这个问题呢?我们知道,面向接口编程是解决耦合问题的法宝,于是有一伙人就定义了一个接口,各种业务类都必须实现这个接口,这个接口就叫Servlet接口,有时我们也把实现了Servlet接口的业务类叫作Servlet。
但是这里还有一个问题,对于特定的请求,HTTP 服务器如何知道由哪个 Servlet 来处理呢?Servlet又是由谁来实例化呢?
显然HTTP服务器不适合做这个工作,否则又和业务类耦合了。
于是,还是那伙人又发明了Servlet容器,Servlet容器用来加载和管理业务类。HTTP服务器不直接跟业务类打交道,而是把请求交给Servlet容器去处理,Servlet容器会将请求转发到具体的Servlet,如果这个Servlet还没创建,就加载并实例化这个Servlet,然后调用这个Servlet的接口方法。因此Servlet接口其实是Servlet容器跟具体业务类之间的接口。
下面我们通过一张图来加深理解:
图的左边表示 HTTP 服务器直接调用具体业务类,它们是紧耦合的。再看图的右边,HTTP 服务器不直接调用业务类,而是把请求交给容器来处理,容器通过 Servlet 接口调用业务类。因此 Servlet 接口和 Servlet 容器的出现,达到了 HTTP 服务器与业务类解耦的目的。
而 Servlet 接口和 Servlet 容器这一整套规范叫作 Servlet 规范。Tomcat 和 Jetty 都按照 Servlet 规范的要求实现了 Servlet 容器,同时它们也具有 HTTP 服务器的功能。作为 Java 程序员,如果我们要实现新的业务功能,只需要实现一个 Servlet,并把它注册到 Tomcat(Servlet 容器)中,剩下的事情就由 Tomcat 帮我们处理了。
接下来我们来看看 Servlet 接口具体是怎么定义的,以及 Servlet 规范又有哪些要重点关注的地方呢?
Servlet接口定义了下面五个方法,Tomcat管理Servlet就是通过这5个方法进行管理(调用)的。
1. void service 方法
public interface Servlet {
void init(ServletConfig config) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
String getServletInfo();
void destroy();
}
其中最重要是的 service 方法,具体业务类在这个方法里实现处理逻辑。这个方法有两个参数:ServletRequest 和 ServletResponse。ServletRequest 用来封装请求信息,ServletResponse 用来封装响应信息,因此本质上这两个类是对通信协议的封装。
比如 HTTP 协议中的请求和响应就是对应了 HttpServletRequest 和 HttpServletResponse 这两个类。你可以通过 HttpServletRequest 来获取所有请求相关的信息,包括请求路径、Cookie、HTTP 头、请求参数等。此外,我们还可以通过 HttpServletRequest 来创建和获取 Session。而 HttpServletResponse 是用来封装 HTTP 响应的。
2. void init和 void destroy方法
接口中还有两个跟生命周期有关的方法 init 和 destroy,这是一个比较贴心的设计,Servlet 容器在加载 Servlet 类的时候会调用 init 方法,在卸载的时候会调用 destroy 方法。我们可能会在 init 方法里初始化一些资源,并在 destroy 方法里释放这些资源,比如 Spring MVC 中的 DispatcherServlet,就是在 init 方法里创建了自己的 Spring 容器。
3. ServletConfig 方法
ServletConfig 的作用就是封装 Servlet 的初始化参数。你可以在web.xml给 Servlet 配置参数,并在程序里通过 getServletConfig 方法拿到这些参数。
我们知道,有接口一般就有抽象类,抽象类用来实现接口和封装通用的逻辑,因此 Servlet 规范提供了 GenericServlet 抽象类,我们可以通过扩展它来实现 Servlet。虽然 Servlet 规范并不在乎通信协议是什么,但是大多数的 Servlet 都是在 HTTP 环境中处理的,因此 Servet 规范还提供了 HttpServlet 来继承 GenericServlet,并且加入了 HTTP 特性。
这样我们通过继承 HttpServlet 类来实现自己的 Servlet,只需要重写两个方法:doGet 和 doPost。
引入了Servlet规范后,你不需要关心Socket网络通信、不需要关心HTTP协议,也不需要关心你的业务类是如何被实例化和调用的,因为这些都被Servlet规范标准化了,你只要关心怎么实现的你的业务逻辑。这对于程序员来说是件好事,但也有不方便的一面。所谓规范就是说大家都要遵守,就会千篇一律,但是如果这个规范不能满足你的业务的个性化需求,就有问题了,因此设计一个规范或者一个中间件,要充分考虑到可扩展性。
Servlet规范提供了两种扩展机制:Filter和Listener。
Spring中有BeanFactory,后置处理器等来进行Spring的功能拓展。Servlet容器也有filter和listener标签中就可以添加很多filter和listener来进行拓展。
Filter过滤器
这个接口允许你对请求和响应做一些统一的定制化处理,比如你可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。过滤器的工作原理是这样的:Web应用部署完成后,Servlet容器需要实例化Filter并把Filter链接成一个FilterChain。当请求进来时,获取第一个Filter并调用doFilter方法,doFilter方法负责调用这个FilterChain中的下一个Filter。
Listener 是监听器
这是另一种扩展机制,当 Web 应用在 Servlet 容器中运行时,Servlet 容器内部会不断的发生各种事件,如 Web 应用的启动和停止、用户请求到达等。Servlet 容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet 容器会负责调用监听器的方法。当然,你可以定义自己的监听 器去监听你感兴趣的事件,将监听器配置在web.xml中。比如 Spring 就实现了自己的监听器,来监听 ServletContext 的启动事件,目的是当 Servlet 容器启 动时,创建并初始化全局的 Spring 容器。
3、Servlet 容器
Tomcat只能通过TCP/IP 协议来接收到请求来的数据,然后呢?我们要把这些数据传给业务层。怎么传这个问题还好说,传到哪才是关键。难道我们要解析请求路径然后不断的if/else 来转发到项目的不同业务上吗? 这样肯定不行的,太麻烦。
所以Servlet 出现了,它定义了接口规范,Servlet 就是为了将HTTP 服务封装好的数据流向业务层的。其中Servlet 为了更好的接收HTTP服务器发来的封装好的数据,它又写了一个HTTPServlet,里面的方法包含了HttpServletRequest、HttpServletResponse参数。
当客户请求某个资源时,HTTP服务器会用一个ServletRequest对象把客户的请求信息封装起来,然后调用Servlet容器的service方法,Servlet容器拿到请求后,根据请求的URL和Servlet的映射关系,找到相应的Servlet,如果Servlet还没有被加载,就用反射机制创建这个Servlet,并调用Servlet的init方法来完成初始化,接着调用Servlet的service方法来处理请求,把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端。
Servlet容器会实例化和调用Servlet,那Servlet是怎么注册到Servlet容器中的呢?一般来说,我们是以Web应用程序的方式来部署Servlet的,而根据Servlet规范,Web应用程序有一定的目录结构,在这个目录下分别放置了Servlet的类文件、配置文件以及静态资源,Servlet容器通过读取配置文件,就能找到并加载Servlet。
Servlet 规范里还定义了 ServletContext 这个接口来对应一个 Web 应用。Web 应用部署好后,Servlet 容器在启动时会加载 Web 应用,并为每个 Web 应用创建唯一的 ServletContext 对象。可以把 ServletContext 看成是一个全局对象,一个 Web 应用可 能有多个 Servlet,这些 Servlet 可以通过全局的 ServletContext 来共享数据,这些数据包括 Web 应用的初始化参数、Web 应用目录下的文件资源等。由于 ServletContext 持有所有 Servlet 实例,你还可以通过它来实现 Servlet 请求的转发。
Server容器案例
Tomcat对BioServer的支持:
拿到客户端的请求数据时,通过线程池来处理客户端连接,处理连接就是解析数据:
4、Cookie 和 Session
我们知道,HTTP 协议有个特点是无状态,请求与请求之间是没有关系的。这样 会出现一个很尴尬的问题:Web 应用不知道你是谁,登录一个网页,我退出了,又要再登一次。因为发送的请求不能让服务器识别出你的身份。因此 HTTP 协议需要一种 技术让请求与请求之间建立起联系,并且服务器需要知道这个请求来自哪个用 户,于是 Cookie 技术出现了。
1)Cookie 技术
Cookie 是 HTTP 报文的一个请求头,Web 应用可以将用户的标识信息或者其他一些信息(用户名等)存储在 Cookie 中。用户经过验证之后,每次 HTTP 请求报文中都包含 Cookie,这样服务器读取这个 Cookie 请求头就知道用户是谁了。Cookie 本质上就是一份存储在用户本地的文件,里面包含了每次请求中都需要传递的信息。
2)Session 技术
由于 Cookie 以明文的方式存储在本地,而 Cookie 中往往带有用户信息,这样就造成了非常大的安全隐患。
而 Session 的出现解决了这个问题,Session 可以理解为服务器端开辟的存储空间,里面保存了用户的状态,用户信息以 Session 的形式存储在服务端。当用户请求到来时,服务端可以把用户的请求和用户的 Session 对应起来。那么 Session 是怎么和请求对应起来的呢?答案是通过 Cookie,浏览器在 Cookie 中填充了一个 Session ID 之类的字段用来标识请求。
具体工作过程是这样的:服务器在创建 Session 的同时,会为该 Session 生成唯一的 Session ID,当浏览器再次发送请求的时候,会将这个 Session ID 带上,服务器接受到请求之后就会依据 Session ID 找到相应的 Session,找到 Session 后,就可以在 Session 中获取或者添加内容了。而这些内容只会保存在服务器中,发到客户端的只有 Session ID,这样相对安全,也节省了网络流量,因为不需要在 Cookie 中存储大量用户信息。
3)Session 创建与存储
那么 Session 在何时何地创建呢?当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同的创建 Session 的方法。
在 Java 中,是 Web 应用程序在调用 HttpServletRequest 的 getSession 方法时,由 Web 容器(比如 Tomcat)创建的。那 HttpServletRequest 又是什么呢?别着急,我们下一期再聊。Tomcat 的 Session 管理器提供了多种持久化方案来存储 Session,通常会采用高性能的存储方式,比如 Redis,并且通过集群部署的方式,防止单点故障,从而提升高可用。同时,Session 有过期时间,因此 Tomcat 会开启后台线程定期的轮询,如果 Session 过期了就将 Session 失效。
5、Tomcat 组件
Tomcat 作为一个 「Http 服务器 + Servlet 容器」,对我们屏蔽了应用层协议和网络通信细节,给我们的是标准的 Request 和 Response 对象;对于具体的业务逻辑则作为变化点,交给我们来实现。我们使用了SpringMVC 之类的框架,可是却从来不需要考虑 TCP 连接、Http 协议的数据处理与响应,就是因为 Tomcat 已经为我们做好了这些,我们只需要关注每个请求的具体业务逻辑。
Tomcat内部也隔离了变化点与不变点,使用了组件化设计,目的就是为了实现「俄罗斯套娃式」的高度定制化(组合模式),而每个组件的生命周期管理又有一些共性的东西,则被提取出来成为接口和抽象类,让具体子类实现变化点,也就是模板方法设计模式。
当今流行的微服务也是这个思路,按照功能将单体应用拆成「微服务」,拆分过程要将共性提取出来,而这些共性就会成为核心的基础服务或者通用库,「中台」思想亦是如此。设计模式往往就是封装变化的一把利器,合理的运用设计模式能让我们的代码与系统设计变得优雅且整洁。
Tomcat整体功能是通过组件的方式拼装完成:
Tomcat的组件:
其中:
server:指的是整个应用的上下文, 也是最顶层的容器,tomcat中所有的东西都在这个server里边。
service:指的是一个服务,主要的功能是把connector组件和engine组织起来,使得通过connector组件与整个容器通讯的应用可以使用engine提供的服务。
engine:服务引擎,这个可以理解为一个真正的服务器,内部提供了多个虚拟主机对外服务。
host:虚拟主机,每一个虚拟主机相当于一台服务器,并且内部可以部署多个应用,每个虚拟主机可以绑定一个域名,并指定多个别名。
context:应用上下文,每一个webapp都有一个单独的context,也可以理解为每一个context代表一个webapp。
connector:连接器组件,连接器主要功能就是接受 TCP/IP 连接,限制连接数然后读取数据,最后将请求转发到 Container 容器, 可以配置多个连接器支持多种协议,如http,APJ 等。
组件说明:
server(服务器):是Tomcat的一个实例,通常一个JVM只能包含一个Tomcat实例;因此,一台物理服务器上可以在启动多个JVM的情况下在每一个JVM中启动一个Tomcat实例,每个实例分属于一个独立的管理端口。这是一个顶级组件。
service(服务):一个服务组件通常包含一个引擎和与此引擎相关联的一个或多个连接器。给服务命名可以方便管理员在日志文件中识别不同服务产生的日志。一个server可以包含多个service组件,但通常情下只为一个service指派一个server。
connectors(连接器):负责连接客户端(可以是浏览器或Web服务器)请求至Servlet容器内的Web应用程序,通常指的是接收客户发来请求的位置及服务器端分配的端口。默认端口通常是HTTP协议的8080,管理员也可以根据自己的需要改变此端口。还可以支持HTTPS ,默认HTTPS端口为8443。同时也支持AJP,即(A)一个引擎可以配置多个连接器,但这些连接器必须使用不同的端口。默认的连接器是基于HTTP/1.1的Coyote。同时,Tomcat也支持AJP、JServ和JK2连接器。
context(上下文):Context组件是最内层次的组件,它表示Web应用程序本身。配置一个Context最主要的是指定Web应用程序的根目录,以便Servlet容器能够将用户请求发往正确的位置。Context组件也可包含自定义的错误页,以实现在用户访问发生错误时提供友好的提示信息。
engine(引擎):引擎通是指处理请求的Servlet引擎组件,即Catalina Servlet引擎,它检查每一个请求的HTTP首部信息以辨别此请求应该发往哪个host或context,并将请求处理后的结果返回的相应的客户端。严格意义上来说,容器不必非得通过引擎来实现,它也可以是只是一个容器。如果Tomcat被配置成为独立服务器,默认引擎就是已经定义好的引擎。而如果Tomcat被配置为Apache Web服务器的提供Servlet功能的后端,默认引擎将被忽略,因为Web服务器自身就能确定将用户请求发往何处。一个引擎可以包含多个host组件。
host(主机):主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN的“虚拟主机”。一个引擎至少要包含一个主机组件。
其他组件:
这类组件通常包含于容器类组件中以提供具有管理功能的服务,它们不能包含其它组件,但有些却可以由不同层次的容器各自配置。
Valve(阀门):用来拦截请求并在将其转至目标之前进行某种处理操作,类似于Servlet规范中定义的过滤器。Valve可以定义在任何容器类的组件中。Valve常被用来记录客户端请求、客户端IP地址和服务器等信息,这种处理技术通常被称作请求转储(request dumping)。请求转储valve记录请求客户端请求数据包中的HTTP首部信息和cookie信息文件中,响应转储valve则记录响应数据包首部信息和cookie信息至文件中。
Logger(日志记录器):用于记录组件内部的状态信息,可被用于除Context之外的任何容器中。日志记录的功能可被继承,因此,一个引擎级别的Logger将会记录引擎内部所有组件相关的信息,除非某内部组件定义了自己的Logger组件。
Realm(领域):用于用户的认证和授权;在配置一个应用程序时,管理员可以为每个资源或资源组定义角色及权限,而这些访问控制功能的生效需要通过Realm来实现。Realm的认证可以基于文本文件、数据库表、LDAP服务等来实现。Realm的效用会遍及整个引擎或顶级容器,因此,一个容器内的所有应用程序将共享用户资源,同时,Realm可以被其所在组件的子组件继承,也可以被子组件中定义的Realm所覆盖。
各组件间的关系:
- erver 和 Service
- Connector 连接器
-
- HTTP 1.1
- SSL https
- AJP( Apache JServ Protocol) apache 私有协议,用于apache 反向代理Tomcat
- Container
-
- Engine 引擎 catalina
- Host 虚拟机 基于域名 分发请求
- Context 隔离各个WEB应用 每个Context的 ClassLoader都是独立
- Component
-
- Manager (管理器)
- logger (日志管理)
- loader (载入器)
- pipeline (管道)
- valve (管道中的阀)
6、Tomcat 整体架构设计
我们知道了Tomcat 有哪些核心组件,组件之间的关系。以及Tomcat是怎么处理一个 HTTP请求的。下面我们通过一张简化的类图来回顾一下,从图上你可以看到各种组件的层次关系,图中的虚线表示一个请求在 Tomcat 中流转的过程。
1)连接器
Tomcat的整体架构包含了两个核心组件连接器和容器。连接器负责对外交流,容器负责内部处理。连接器用 ProtocolHandler接口来封装通信协议和 I/O模型的差异,ProtocolHandler内部又分为 EndPoint和 Processor模块,EndPoint负责底层 Socket通信,Proccesor负责应用层协议解析。连接器通过适配器 Adapter调用容器。
对 Tomcat 整体架构的学习,我们可以得到一些设计复杂系统的基本思路。首先要分析需求,根据高内聚低耦合的原则确定子模块,然后找出子模块中的变化点和不变点,用接口和抽象基类去封装不变点,在抽象基类中定义模板方法,让子类自行实现抽象方法,也就是具体子类去实现变化点。
2)容器
运用了组合模式管理容器、通过 观察者模式发布启动事件达到解耦、开闭原则。骨架抽象类和模板方法抽象变与不变,变化的交给子类实现,从而实现代码复用,以及灵活的拓展。使用责任链的方式处理请求,比如记录日志等。
3)类加载器
类加载的本质是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件 .class,也可以是 jar 包里的 .class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。
JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。
Tomcat 的自定义类加载器 WebAppClassLoader为了隔离 Web 应用打破了双亲委托机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载 Web 应用自己定义的类。防止 Web 应用自己的类覆盖 JRE 的核心类,使用 ExtClassLoader 去加载,这样即打破了双亲委派,又能安全加载。
7、Tomcat 执行流程
启动Tomcat命令startup.bat启动了BootStrap启动类,启动类里有main方法进行初始化加载配置文件。Tomcat启动后创建Server对象(根实例),Server创建Service(服务组件)接收用户请求和处理用户请求。
Service内分为Connector连接器组件和Container容器组件:
① Connector连接器组件
监听端口,根据协议封装请求数据,创建Request与Response对象交给Container容器组件处理。
连接器可以有多个,默认配置两个连接器,监听8080端口的http协议,监听8009端口的ajp协议,使用apache服务器使用的是ajp协议。
1)Connector连接器的作用
1. 监听端口数据;
2. 根据协议解析数据;
3. 封装成Request和Response对象;
4. 将请求交给容器执行;
2)连接器内部组件
ProtocolHandler(协议处理器):监听端口,根据协议封装数据。
ProtocolHander(协议处理器):支持两种协议: AJP、Http协议。
ProtocolHander(协议处理器):支持的IO模型: NIO、NIO2、APR(使用C/C++编写的)。
3)ProtocolHandler内部有6个实现类
AjpNioProtocol
AjpAprProtocol
AjpNio2Protocol
Http11NioProtocol
Http11Nio2Protocol
Http11AprProtocol
4)支持两种协议处理
Http协议、Ajp协议(用于和Web服务器集成(如Apache),以实现对静态资源的优化以及集群部署)
5)共有三种处理方式
Nio、Nio2、Apr
6)Adapter(适配器)
通过映射得到是否存在本次请求对应的资源,存在会将Request、Response对象进一步封装,封装成ServletRequest、ServletResponse对象,调用容器执行,不存在返回404异常。
② Container容器组件
在 Tomcat 里,容器就是用来装载 Servlet 的。
四个组件具有层级关系:Engine、HOST、Context、Wrapper。
各个组件的含义 :
一个Tomcat中有一个大的容器Engine引擎,Engine引擎接口继承了Container容器接口 ,Engine引擎内存储多个Host虚拟主机,一个Host虚拟主机内存储多个Context,一个Context内存储多个Wrapper。
一个Context代表一个项目,Wrapper代表一个Servlet容器组件调用我们写的servlet程序,HOST虚拟主机容器。
我们也可以再通过Tomcat的server.xml配置文件来加深对Tomcat容器的理解。Tomcat采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件按照一定的格式要求配置在这个顶层容器中。
那么,Tomcat是怎么管理这些容器的呢?你会发现这些容器具有父子关系,形成一个树形结构,你可能马上就想到了设计模式中的组合模式。没错,Tomcat就是用组合模式来管理这些容器的。具体实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的Wrapper,组合容器对象指的是上面的Context、Host或者Engine。
③ Tomcat请求的流程
用户发送请求,被Tomcat服务器内的连接器接收到,监听到端口,根据指定协议进行封装,例如Http协议,封装成Request、Response对象,通过适配器映射得到是否存在本次请求对应的资源,存在会将Request、Response对象进一步封装,封装成ServletRequest、ServletResponse对象,调用容器执行,不存在返回404异常。
调用容器中引擎根据路径中的域名找到具体的虚拟主机,虚拟主机根据路径中项目名称找到具体的Context应用程序,应用程序根据路径中资源名找到对应的Servlet,Wrapper组件内封装的就是Servlet,调用我们写的Sevlet方法执行,执行完返回给用户。
整个请求流程:
1. 用户点击网页内容,请求被发送到本机端口80,被在那里监听的Connector获得。
2. Connector会根据 http协议封装该请求,创建Request 和 Response对象。
3. 将Request 和 Response 交给 Adapter适配器处理。
4. Adapter适配器查找Mapper缓存 判断资源是否存在。
存在:
封装Request 为 ServletRequest;
封装Response 为 ServletResponse;
并把请求交给当前所在Service的Engine容器来处理并等待Engine的处理结果;
不存在:
报404异常;
5. Engine获得请求 http://www.shopping.cn/bbs/findAll,匹配所有的虚拟主机Host。
6. Engine匹配到名为www.shopping.cn的Host(匹配不到把请求交给默认的Host处理),名为www.shopping.cn的Host获得请求/bbs/findAll,匹配它所拥有的所有的Context。Host匹配到路径为/bbs的Context(如果匹配不到就把该请求交给路径名为"/"的Context去处理)。
7. path=“/bbs”的Context获得请求/findAll,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为 findAll 的Servlet。
8. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用servlet 的 doGet()或doPost().执行业务逻辑、数据存储等程序。
9. Context把执行完之后的HttpServletResponse对象返回给Host。
10. Host把HttpServletResponse对象返回给Engine。
11. Engine把HttpServletResponse对象返回Connector。
12. Connector把HttpServletResponse对象返回给客户Browser。
四、Tomcat 服务器高级配置
1、Tomcat 目录结构
1)Tomcat主目录介绍
cd /usr/local/tomcat/
tree -L 1
.
├── bin #存放tomcat管理脚本
├── conf # tomcat 配置文件存放目录
├── lib # web应用调用的jar包存放路径
├── LICENSE
├── logs # tomcat 日志存放目录,catalina.out 为主要输出日志
├── NOTICE
├── RELEASE-NOTES
├── RUNNING.txt
├── temp # 存放临时文件
├── webapps # web程序存放目录
└── work # 存放编译产生的.java 与 .class文件
2)Webapps目录介绍
.
├── docs # tomcat 帮助文档
├── examples # web应用示例
├── host-manager # 主机管理
├── manager # 管理
└── ROOT # 默认站点根目录
//默认网站的主目录(主页) /usr/local/tomcat/webapps/ROOT
webapps目录用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包、jar包的形式发布应用。
当然,你也可以把应用程序放置在磁盘的任意位置,在配置文件中映射好就行。
3)Tomcat配置文件目录介绍(conf)
.
├── Catalina
├── catalina.policy
├── catalina.properties
├── context.xml
├── logging.properties
├── logs
├── server.xml # tomcat 主配置文件
├── server.xml.bak
├── server.xml.bak2
├── tomcat-users.xml # tomcat 管理用户配置文件
├── tomcat-users.xsd
└── web.xml
配置详细说明:
1. catalina.policy
项目安全文件,用来防止欺骗代码或JSP执行带有像System.exit(0)这样的命令的可能影响容器的破坏性代码. 只有当Tomcat用-security命令行参数启动时这个文件才会被使用,即启动tomcat时, startup.sh -security 。
上图中,tomcat容器下部署两个项目,项目1和项目2。由于项目1中有代码System.exit(0),当访问该代码时,该代码会导致整个tomcat停止,从而也导致项目2停止。
为了解决因项目1存在欺骗代码或不安全代码导致损害Tomcat容器,从而影响其他项目正常运行的问题,启动tomcat容器时,加上-security参数就,即startup.sh -security,如此即使项目1中有代码System.exit(0),也只会仅仅停止项目1,而不会影响Tomcat容器,然而起作用的配置文件就是catalina.policy文件。
2. catalina.properties
配置tomcat启动相关信息文件。
3. context.xml
监视并加载资源文件,当监视的文件发生发生变化时,自动加载。
4. jaspic-providers.xml 和 jaspic-providers.xsd
这两个文件不常用。
5. logging.properties
该文件为tomcat日志文件,包括配置tomcat输出格式,日志级别等。
6. server.xml
tomcat核心架构主件文件,下面会详细解析。
7. tomcat-users.xml和tomcat-users.xsd
tomcat用户文件,如配置远程登陆账号,tomcat-users.xsd 为tomcat-users.xml描述和约束文件。
8. web.xml
tomcat全局配置文件。
4)lib目录
lib文件夹主要用来存放tomcat依赖jar包,如下为 tomcat 的lib文件夹下的相关jar包。
每个jar包功能,这里就不讲解了,这里主要分析ecj-4.13.jar,这个jar包起到将.java编译成.class字节码作用。
假设要编译MyTest.java,那么jdk会执行两步:
- 第一步:将MyTest.java编译成MyTest.class
javac MyTest.java
- 第二步:执行MyTest.class
java MyTest.class
- 那么,使用ecj-4.13.jar如执行MyTest.java
java -jar ecj-4.13.jar MyTest.java
5)temp目录
temp目录用户存放tomcat在运行过程中产生的临时文件。(清空不会对tomcat运行带来影响)。
6)work目录
work目录用来存放tomcat在运行时的编译后文件,例如JSP编译后的文件。
清空work目录,然后重启tomcat,可以达到清除缓存的作用。
7)Tomcat日志说明
logs目录表示tomcat日志文件,大致包括如下六类文件:
① catalina.out 和 catalina.log 的区别
catalina.out即标准输出和标准出错,所有输出到这两个位置的都会进入catalina.out,这里包含tomcat运行自己输出的日志以及应用里向console输出的日志。
catalina.out其实是tomcat的标准输出(stdout)和标准出错(stderr),这是在tomcat的启动脚本里指定的,如果没有修改的话stdout和stderr会重定向到这里。所以我们在应用里使用System.out打印的东西都会到这里来。另外,如果我们在应用里使用其他的日志框架,配置了向Console输出的,则也会在这里出现。比如以logback为例,如果配置ch.qos.logback.core.ConsoleAppender则会输出到catalina.out里。
② cataliana.{yyyy-MM-dd}.log和localhost.{yyyy-MM-dd}.log
这两个日志都是通过logging.properties配置的(默认情况下,启动脚本里指定了java.util.logging.config.file和java.util.logging.manager两个变量)。一个典型的logging.properties可能如下所示:
handlers = 1catalina.org.apache.juli.FileHandler, 2localhost.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler
.handlers = 1catalina.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler
1catalina.org.apache.juli.FileHandler.level = INFO
1catalina.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.FileHandler.prefix = catalina.
2localhost.org.apache.juli.FileHandler.level = FINE
2localhost.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.FileHandler.prefix = localhost.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.FileHandler
这个文件大致的意思是,root输出到catalina和console。而这里的catalina按照配置对应的是catalina.{yyyy-MM-dd}.log,这里的console最终会输出到catalina.out。这就是我们看到catalina.{yyyy-MM-dd}.log和catalina.out的日志很多都是一样的原因。
配置文件中还有一个localhost,所有logname或parent logname为org.apache.catalina.core.ContainerBase.[Catalina].[localhost]的都会输出到localhost.{yyyy-MM-dd}.log文件。而这个logname又代表着什么呢?在tomcat中有一个server.xml的配置文件,其中有这么一个片段:
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="false" autoDeploy="false">
</Host>
</Engine>
我们可以这么简单的理解: 一个Tomcat进程对应着一个Engine,一个Engine下可以有多个Host(Virtual Host),一个Host里可以有多个Context,比如我们常常将应用部署在ROOT下还是webapps里其他目录,这个就是Context。
这其中Engine对应着tomcat里的StandardEngine类,Host对应着StandardHost类,而Context对应着StandardContext。这几个类都是从ContainerBase派生。这些类里打的一些跟应用代码相关的日志都是使用ContainerBase里的getLogger,而这个这个logger的logger name就是: org.apache.catalina.core.ContainerBase.[current container name].[current container name]...
而我们一个webapp里listener、filter、servlet的初始化就是在StandardContext里进行的,比如ROOT里有一个listener初始化出异常了,打印日志则logger name是org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/]。
这其中Catalina和localhost是上面xml片段里的Engine和Host的name,而[/]是ROOT对应的StandardContext的name。所以listener、filter、servlet初始化时的日志是需要看localhost.{yyyy-MM-dd}.log这个日志的。比如现在我们使用Spring,Spring的初始化我们往往是使用Spring提供的一个listener进行的,而如果Spring初始化时因为某个bean初始化失败,导致整个应用没有启动,这个时候的异常日志是输出到localhost中的,而不是cataina.out中。
所以有的时候我们应用无法启动了,然后找catalina.out日志,但最后也没有定位根本原因是什么,就是因为我们找的日志不对。但有的时候catalina.out里也有我们想要的日志,那是因为我们的应用或使用的一些组件自己捕获了异常,然后将其打印了,这个时候如果恰好这些日志被我们配置成输出到console,则这些日志也会在catalina.out里出现了。
查看日志:
tailf /usr/local/tomcat/logs/catalina.out
24-Nov-2017 15:09:51.654 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
24-Nov-2017 15:09:51.665 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
24-Nov-2017 15:09:51.670 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 60037 ms
发现启动时间较长,其中有一项的启动时间占据了绝大多数:
24-Nov-2017 15:09:50.629 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive /application/apache-tomcat-8.0.27/webapps/memtest.war has finished in 58,892 ms
发现耗时在这里:是session引起的随机数问题导致的。Tocmat的Session ID是通过SHA1算法计算得到的,计算Session ID的时候必须有一个密钥,为了提高安全性Tomcat在启动的时候会通过随机生成一个密钥。
解决Tomcat启动慢的方法:
Tomcat启动慢主要原因是生成随机数的时候卡住了,导致tomcat启动不了,是否有足够的值来用于产生随机数,可以通过如下命令来查看。
cat /proc/sys/kernel/random/entropy_avail
解决方法:
vim $JAVA_HOME/jre/lib/security/java.security
securerandom.source=file:/dev/random 改为
securerandom.source=file:/dev/urandom
tomcat中运行的生产项目,出现问题如何排查?
catalina.out即标准输出和标准出错,所有输出到这两个位置的都会进入catalina.out,这里包含tomcat运行自己输出的日志以及应用里向console输出的日志。catalina.{yyyy-MM-dd}.log是tomcat自己运行的一些日志,这些日志还会输出到catalina.out,但是应用向console输出的日志不会输出到catalina.{yyyy-MM-dd}.log。localhost.{yyyy-MM-dd}.log主要是应用初始化(listener, filter, servlet)未处理的异常最后被tomcat捕获而输出的日志,而这些未处理异常最终会导致应用无法启动。
出现问题首先去tomcat的logs目录下,查看日志文件中有没有什么错误信息:
catalina.202X-XX-XX.log记录启动时日志信息。
localhost.202X-XX-XX.log记录各个webapp下项目的日志信息。
tail -f fileName
#会把 filename 文件里的最尾部的内容显示在屏幕上,并且不断刷新,只要 filename 更新就可以看到最新的文件内容。
如:日志中发现
java.lang.StackOverflowError异常,这个是栈内存溢出。
可能在哪些地方发生内存泄漏了,如果通过异常能够找到溢出的发生地,则优化对应代码。
如果没有发现,则可以通过 JvisualVM 等工具监控Tomcat的运行情况。
最后想想,这里分几个日志文件其实不利于问题查找,为啥不干脆都输出到catalina.out里呢?tomcat作为通用容器本身,可能考虑到Engine下有多个Host,每个Host的日志还是要输出到不同的文件。而实际中我们往往是单容器,单Host,甚至是只有一个ROOT的Context。
2、Tomcat 服务器配置
Tomcat 服务器的配置主要集中于 tomcat/conf 下的 catalina.policy、catalina.properties、context.xml、server.xml、tomcat-users.xml、web.xml 文件。
1)server.xml
server.xml 是tomcat 服务器的核心配置文件,包含了Tomcat的 Servlet 容器(Catalina)的所有配置。由于配置的属性特别多,我们在这里主要讲解其中的一部分重要配置。
基本配置:
<Server>
<Listener /><!-- 监听器 -->
<GlobaNamingResources> <!-- 全局资源 -->
</GlobaNamingResources
<Service> <!-- 服务 用于 绑定 连接器与 Engine -->
<Connector 8080/> <!-- 连接器-->
<Connector 8010 /> <!-- 连接器-->
<Connector 8030/> <!-- 连接器-->
<Engine> <!-- 执行引擎-->
<Logger />
<Realm />
<host "www.test.com" appBase=""> <!-- 虚拟主机-->
<Logger /> <!-- 日志配置-->
<Context "/applction" path=""/> <!-- 上下文配置-->
</host>
</Engine>
</Service>
</Server>
① Server
Server是server.xml的根元素,用于创建一个Server实例,默认使用的实现类是 org.apache.catalina.core.StandardServer。
<Server port="8005" shutdown="SHUTDOWN">
...
</Server>
Server的相关属性:
className: 用于实现此Server容器的完全限定类的名称,默认为org.apache.catalina.core.StandardServer;
port: 接收shutdown指令的端口,默认仅允许通过本机访问,默认为8005;
shutdown:发往此Server用于实现关闭tomcat实例的命令字符串,默认为SHUTDOWN;
Server内嵌的子元素为 Listener、GlobalNamingResources、Service。
默认配置的5个Listener 的含义:
<!-- 用于以日志形式输出服务器 、操作系统、JVM的版本信息 -->
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- 用于加载(服务器启动) 和 销毁 (服务器停止) APR。 如果找不到APR库, 则会输出日志, 并不影响Tomcat启动 -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- 用于避免JRE内存泄漏问题 -->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<!-- 用户加载(服务器启动) 和 销毁(服务器停止) 全局命名服务 -->
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<!-- 用于在Context停止时重建Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 -->
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
GlobalNamingResources 中定义了全局命名服务:
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<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 实例,将多个connector 与一个Engine组合成一个服务,可以配置多个服务。默认使用 org.apache.catalina.core.StandardService, 默认情况下Tomcat 仅指定了Service 的名称, 值为 "Catalina"。
Service 可以内嵌的元素为 : Listener、Executor、Connector、Engine,其中 Listener 用于为Service添加生命周期监听器, Executor 用于配置Service 共享线程池,Connector 用于配置Service 包含的连接器, Engine 用于配置Service中连接器对应的Servlet 容器引擎。
<Service name="Catalina">
...
</Service>
一个Server服务器,可以包含多个Service服务。
这定义了一个名为Catalina的Service,此名字也会在产生相关的日志信息时记录在日志文件当中。
Service相关的属性:
className: 用于实现service的类名,一般都是org.apache.catalina.core.StandardService。
name:此服务的名称,默认为Catalina;
③ Executor
默认情况下,Service 并未添加共享线程池配置。默认Http和Ajp协议各自创建10个线程。
如果我们想添加一个线程池, 可以在 下添加如下配置:
<Executor name="tomcatThreadPool"
namePrefix="catalina-exec-"
maxThreads="200"
minSpareThreads="100"
maxIdleTime="60000"
maxQueueSize="Integer.MAX_VALUE"
prestartminSpareThreads="false"
threadPriority="5"
className="org.apache.catalina.core.StandardThreadExecutor"/>
属性说明:
属性 | 含义 |
---|---|
name | 线程池名称,用于 Connector中指定。 |
namePrefix | 所创建的每个线程的名称前缀,一个单独的线程名称为 namePrefix+threadNumber。 |
maxThreads | 池中最大线程数。 |
minSpareThreads | 活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在。 |
maxIdleTime | 线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位毫秒。 |
maxQueueSize | 在被执行前最大线程排队数目,默认为Int的最大值,也就是广义的无限。除非特殊情况,这个值不需要更改,否则会有请求不会被处理的情况发生。 |
prestartminSpareThreads | 启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动。 |
threadPriority | 线程池中线程优先级,默认值为5,值从1到10。 |
className | 线程池实现类,未指定情况下,默认实现类为org.apache.catalina.core.StandardThreadExecutor。如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口。 |
如果不配置共享线程池,那么Catalina 各组件在用到线程池时会独立创建。
添加共享线程池:
<Executor name="tomcatThreadPool"
namePrefix="catalina-exec-"
maxThreads="200"
minSpareThreads="100"
maxIdleTime="60000"
maxQueueSize="Integer.MAX_VALUE"
prestartminSpareThreads="false"
threadPriority="5"
className="org.apache.catalina.core.StandardThreadExecutor"/>
<!--两个协议都要改才可以-->
<Connector port="8080" executor="tomcatThreadPool" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" executor="tomcatThreadPool" protocol="AJP/1.3" redirectPort="8443" />
④ Connector
连接器用于接收指定协议下的连接 并指定给唯一的Engine 进行处理,Connector用于创建连接器实例。默认情况下,server.xml 配置了两个链接器,一个支持HTTP协议,一个支持AJP协议。因此大多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进行优化。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
属性说明:
1) port: 端口号,Connector 用于创建服务端Socket 并进行监听, 以等待客户端请求链接。如果该属性设置为0,Tomcat将会随机选择一个可用的端口号给当前Connector 使用。
2) protocol : 当前Connector 支持的访问协议。 默认为 HTTP/1.1 , 并采用自动切换机制选择一个基于 JAVA NIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)。
如果不希望采用上述自动切换的机制, 而是明确指定协议, 可以使用以下值。
Tomcat文件中conf目录下server.xml配置NIO2:
<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
redirectPort="8443" />
Http协议:
org.apache.coyote.http11.Http11NioProtocol , 非阻塞式 Java NIO 链接器
org.apache.coyote.http11.Http11Nio2Protocol , 非阻塞式 JAVA NIO2 链接器
org.apache.coyote.http11.Http11AprProtocol , APR 链接器
AJP协议 :
org.apache.coyote.ajp.AjpNioProtocol , 非阻塞式 Java NIO 链接器
org.apache.coyote.ajp.AjpNio2Protocol ,非阻塞式 JAVA NIO2 链接器
org.apache.coyote.ajp.AjpAprProtocol , APR 链接器
3) connectionTimeOut : Connector 接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。
4) redirectPort:当前Connector 不支持SSL请求, 接收到了一个请求, 并且也符合security-constraint 约束, 需要SSL传输,Catalina自动将请求重定向到指定的端口。
5) executor : 指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads 等属性配置内部线程池。
6) URIEncoding : 用于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-8859-1。
7) minThread 服务器启动时创建的处理请求的线程数。
8)maxThread 最大可以创建的处理请求的线程数。
9)enableLookups 如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址。
10)acceptCount 指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理,默认100。
11)address 绑定客户端特定地址,127.0.0.1。
12)bufferSize 每个请求的缓冲区大小 bufferSize * maxThreads。
13)compression 是否启用文档压缩。
14)compressionMinSize 文档压缩的最小大小。
15)compressableMimeTypes text/html,text/xml,text/plain。
16)connectionTimeout 客户端发起链接到服务端接收为止,指定超时的时间数(以毫秒为单位)。
17)connectionUploadTimeout upload情况下连接超时时间。
18)disableUploadTimeout 如果为true则使用 connectionTimeout。
19)keepAliveTimeout 当长链接闲置 指定时间主动关闭 链接 ,前提是客户端请求头 带上这个 head"connection" " keep-alive"。
20)maxKeepAliveRequests 最大的 长连接数 默认最大100。
21)maxSpareThreads BIO 模式下 最多线闲置线程数。
22)minSpareThreads BIO 模式下 最小线闲置线程数
23)SSLEnabled 是否开启 sll 验证,在Https 访问时需要开启。
Tomcat connector 并发参数解读:
完整的配置如下:
<Connector port="8080"
protocol="HTTP/1.1"
executor="tomcatThreadPool"
maxThreads="1000"
minSpareThreads="100"
acceptCount="1000"
maxConnections="1000"
connectionTimeout="20000"
compression="on"
compressionMinSize="2048"
disableUploadTimeout="true"
redirectPort="8443"
URIEncoding="UTF-8" />
配置多个Connector:
<Connector port="8860" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8862"
URIEncoding="UTF-8"
useBodyEncodingForURI="true"
compression="on" compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
maxThreads="1024" minSpareThreads="200"
acceptCount="800"
enableLookups="false"
/>
⑤ Engine
用于处理连接的执行器,默认的引擎是catalina,一个service 中只能配置一个Engine。Engine 作为Servlet 引擎的顶级元素,内部可以嵌入: Cluster、Listener、Realm、Valve和Host。
<Engine name="Catalina" defaultHost="localhost">
...
</Engine>
属性说明:
1) name: 用于指定Engine 的名称, 默认为Catalina 。该名称会影响一部分Tomcat的存储路径(如临时文件)。
2) defaultHost : 默认使用的虚拟主机名称, 当客户端请求指向的主机无效时, 将交由默认的虚拟主机处理, 默认为localhost。
⑥ Host
Host 元素用于配置一个虚拟主机, 类似于nginx 当中的server,默认的虚拟机是localhost。它支持以下嵌入元素:Alias、Cluster、Listener、Valve、Realm、Context。如果在Engine下配置Realm, 那么此配置将在当前Engine下的所有Host中共享。 同样,如果在Host中配置Realm , 则在当前Host下的所有Context中共享。Context中的Realm优先级 > Host 的Realm优先级 > Engine中的Realm优先级。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
...
</Host>
配置多个Host:
<Host name="www.test.com" appBase="/usr/www/test"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="www.luban.com.access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
属性说明:
1) name: 当前Host通用的网络名称, 必须与DNS服务器上的注册信息一致。 Engine中包含的Host必须存在一个名称与Engine的defaultHost设置一致。
2) appBase: 当前Host的应用基础目录, 当前Host上部署的Web应用均在该目录下(可以是绝对目录,相对路径)。默认为webapps。
3) unpackWARs: 设置为true, Host在启动时会将appBase目录下war包解压为目录。设置为false, Host将直接从war文件启动。
4) autoDeploy: 控制tomcat是否在运行时定期检测并自动部署新增或变更的web应用。
通过给Host添加别名,我们可以实现同一个Host拥有多个网络名称,配置如下:
<Host name="www.web1.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Alias>www.web2.com</Alias>
</Host>
这个时候,我们就可以通过两个域名访问当前Host下的应用(需要确保DNS或hosts中添加了域名的映射配置)。
⑦ Context
应用上下文,Context 用于配置一个Web应用。一个host 下可以配置多个Context ,每个Context 都有其独立的classPath,相互隔离,以免造成ClassPath 冲突。
默认的配置如下:
<Context docBase="myApp" path="/myApp">
....
</Context>
属性描述:
1) docBase:Web应用目录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的相对路径。
2) path:Web应用的Context 路径。如果我们Host名为localhost, 则该web应用访问的根路径为: http://localhost:8080/myApp。
它支持的内嵌元素为:CookieProcessor, Loader, Manager,Realm,Resources,WatchedResource,JarScanner,Valve。
Host属性中配置进行项目部署:
<Host name="www.tomcat.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!--指定路径项目部署-->
<Context docBase="D:\servlet_project03" path="/myApp"></Context>
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
属性描述:
1) docBase:Web应用目录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的相对路径。
2) path:Web应用的Context 路径。如果我们Host名为localhost, 则该web应用访问的根路径为: http://localhost:8080/myApp。
⑧ Valve
阀门:可以理解成request 的过滤器,具体配置要基于具体的Valve 接口的子类。以下即为一个访问日志的Valve。
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="www.luban.com.access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
3、Tomcat 启动参数配置
1)Tomcat启动参数设置
我们平时启动Tomcat过程是怎么样的?
- 复制WAR包至Tomcat webapp 目录。
- 执行starut.bat 脚本启动。
- 启动过程中war 包会被自动解压装载。
但是我们在Eclipse 或idea 中启动WEB项目的时候 也是把War包复杂至webapps 目录解压吗?显然不是,其真正做法是在Tomcat程序文件之外创建了一个部署目录,在一般生产环境中也是这么做的 即:Tomcat 程序目录和部署目录分开 。
我们只需要在启动时指定CATALINA_HOME 与 CATALINA_BASE 参数即可实现。
可以编写一个脚本 来实现自定义配置:
更新启动脚本:
#!/bin/bash -e
export now_time=$(date +%Y-%m-%d_%H-%M-%S)
echo "deploy time:$now_time"
app=$1
version=$2
mkdir -p war/
#从svn下载程序至 war目录
war=war/${app}_${version}.war
echo "$war"
svn export svn://192.168.0.253/release/${app}_${version}.war $war
deploy_war() {
#解压版本至当前目录
target_dir=war/${app}_${version}_${now_time}
unzip -q $war -d $target_dir
rm -f appwar
ln -sf $target_dir appwar
target_ln=`pwd`/appwar
echo '<?xml version="1.0" encoding="UTF-8" ?>
<Context docBase="'$target_ln'" allowLinking="false">
</Context>' > conf/Catalina/localhost/ROOT.xml
#重启Tomcat服务
./tomcat.sh restart
}
deploy_war
```
自动部署脚本:
#!/bin/bash -e
export now_time=$(date +%Y-%m-%d_%H-%M-%S)
echo "deploy time:$now_time"
app=$1
version=$2
mkdir -p war/
#从svn下载程序至 war目录
war=war/${app}_${version}.war
echo "$war"
svn export svn://192.168.0.253/release/${app}_${version}.war $war
deploy_war() {
#解压版本至当前目录
target_dir=war/${app}_${version}_${now_time}
unzip -q $war -d $target_dir
rm -f appwar
ln -sf $target_dir appwar
target_ln=`pwd`/appwar
echo '<?xml version="1.0" encoding="UTF-8" ?>
<Context docBase="'$target_ln'" allowLinking="false">
</Context>' > conf/Catalina/localhost/ROOT.xml
#重启Tomcat服务
./tomcat.sh restart
}
deploy_war
```
2)JVM配置
最常见的JVM配置当属内存分配,因为在绝大多数情况下,JVM默认分配的内存可能不能够满足我们的需求,特别是在生产环境,此时需要手动修改Tomcat启动时的内存参数分配。
JVM内存模型图:
JVM配置选项:
windows 平台(bin/catalina.bat)。
在catalina.bat文件中JAVA_OPTS参数上方加入下面的参数。
set JAVA_OPTS=-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8
linux 平台(bin/catalina.sh)。
JAVA_OPTS="-server
-Xms1024m -Xmx2048m
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
-XX:SurvivorRatio=8"
参数说明 :
序号 | 参数 | 含义 |
---|---|---|
1 | -Xms | 堆内存的初始大小 |
2 | -Xmx | 堆内存的最大大小 |
3 | -Xmn | 新生代的内存大小,官方建议是整个堆得3/8。 |
4 | -XX:MetaspaceSize | 元空间内存初始大小, 在JDK1.8版本之前配置为 -XX:PermSize(永久代) |
5 | -XX:MaxMetaspaceSize | 元空间内存最大大小, 在JDK1.8版本之前配置为 -XX:MaxPermSize(永久代) |
6 | -XX:InitialCodeCacheSize -XX:ReservedCodeCacheSize | 代码缓存区大小 |
7 | -XX:NewRatio | 设置新生代和老年代的相对大小比例。这种方式的优点是新生代大小会随着整个堆大小动态扩展。如 -XX:NewRatio=3 指定老年代 / 新生代为 3/1。 老年代占堆大小的 3/4,新生代占 1/4 。 |
8 | -XX:SurvivorRatio | 指定伊甸园区 (Eden) 与幸存区大小比例。如 -XX:SurvivorRatio=10 表示伊甸园区 (Eden) 是 幸存区 To 大小的 10 倍 (也是幸存区 From 的 10 倍)。 所以, 伊甸园区 (Eden) 占新生代大小的 10/12, 幸存区 From 和幸存区 To 每个占新生代的 1/12 。 注意, 两个幸存区永远是一样大的。 |
9 | -server | 启用JDK的server版本 |
配置之后, 重新启动Tomcat ,访问:http://localhost:8080/manager/status
4、修改Tomcat默认端口号
tomcat默认的端口是8080,修改Server.xml中 Connector的port属性即可。
如果同一个tomcat要启动多个tomcat ,还需要修改Server里面的port端口以及Connector AJP协议的port端口。
<Server port="8006" shutdown="SHUTDOWN">
<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
5、tomcat中部署项目的方式
部署的三种方案:
1. 直接将web项目 或 war包 拷贝至webapps目录下即可。
2. 在配置文件Server.xml中找到标签,在里面 加上下面一句:
<Context docBase="项目的路径" path="虚拟目录" />
3. conf/Catalina/localhost 创建 xxx.xml文件,在文件中指定项目路径,该项目的访问路径 即文件的名字:
<Context docBase="项目的路径" />
ROOT文件夹内是根目录下的项目,访问该项目不需要加虚拟目录。
6、tomcat加入系统服务
① linux
创建tomcat.servic 文件:
vim /usr/lib/systemd/system/tomcat.service
[Unit]
Description=tomcat8
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
Environment="JAVA_HOME=/usr/local/java/jdk1.8.0_231"
ExecStart=/usr/local/tomcat/bin/catalina.sh start
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
使用systemctl命令添加开机启动:
systemctl enable tomcat #开机启动tomcat服务
systemctl start tomcat #启动tomcat服务器
systemctl restart tomcat #重启tomcat服务
systemctl stop tomcat · #停止tomcat服务
systemctl disable tomcat #禁止开机启动
② windows
设置系统的环境变量java_home , catalina_home:
java_home 为:D:\software\Java\jdk7
catalina_home 为:D:\software\apache-tomcat-7.0.2
以上操作也可以通过在dos命令中完成:
set java_home = D:\software\Java\jdk7
set catalina_home = D:\software\apache-tomcat-7.0.2
修改tomcat中的批处理文件:
修改D:\software\apache-tomcat-7.0.2\bin\service.bat文件
在该文件前面加入:
set SERVICE_NAME=Tomcat7
set PR_DISPLAYNAME=Apache Tomcat
通过cmd命令行进入tomcat的bin目录下执行service.bat install安装,通过service remove tomcat7卸载服务,执行完成后在系统服务中将tomcat对应的服务启动方式设置为自动即可!
7、配置多实例
1. 关闭主站
bash /usr/local/tomcat/bin/shutdown.sh
2. 准备多实例主目录
mkdir /usr/local/tomcat/instance{1..3}
3. 制作实例工作目录
# cp -r /usr/local/tomcat/{conf,logs,temp,work} /usr/local/tomcat/instance1/
# cp -r /usr/local/tomcat/{conf,logs,temp,work} /usr/local/tomcat/instance2/
# cp -r /usr/local/tomcat/{conf,logs,temp,work} /usr/local/tomcat/instance3/
查看目录结构
tree -d -L 2 /usr/local/tomcat/
/usr/local/tomcat/
├── bin
├── instance1
│ ├── conf
│ ├── logs
│ ├── temp
│ └── work
├── instance2
│ ├── conf
│ ├── logs
│ ├── temp
│ └── work
├── instance3
│ ├── conf
│ ├── logs
│ ├── temp
│ └── work
└── lib
4. 修改端口
将web配置文件拷贝三份。分别修改为不同端口
vim /usr/local/tomcat/instance1/conf/server.xml
服务端口 8080 替换成 8081
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
程序工作端口:8005替换成 8091
<Server port="8091" shutdown="SHUTDOWN">
网站目录:webapps 替换成 另一个目录
appBase="/webapps"
vim /usr/local/tomcat/instance2/conf/server.xml
服务端口 8080 替换成 8082
<Connector port="8082" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
程序工作端口:8005替换成 8092
<Server port="8092" shutdown="SHUTDOWN">
网站目录:webapps 替换成 另一个目录
appBase="/webapps"
vim /usr/local/tomcat/instance2/conf/server.xml
服务端口 8080 替换成 8083
<Connector port="8083" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
程序工作端口:8005替换成 8093
<Server port="8093" shutdown="SHUTDOWN">
网站目录:webapps 替换成 另一个目录
appBase="/webapps"
5. 启动脚本
vim /usr/local/tomcat/instance1/ins1.sh
#!/bin/bash
#instance1
. /etc/init.d/functions
#functions系统自带脚本。功能函数
export CATALINA_BASE="/usr/local/tomcat/instance1"
case "$1" in
start)
$CATALINA_HOME/bin/startup.sh
;;
stop)
$CATALINA_HOME/bin/shutdown.sh
;;
restart)
$CATALINA_HOME/bin/shutdown.sh
sleep 5
$CATALINA_HOME/bin/startup.sh
;;
esac
export JAVA_OPTS='-Xms64m -Xmx128m'
vim /usr/local/tomcat/instance1/ins2.sh
#!/bin/bash
#instance2
. /etc/init.d/functions
#functions系统自带脚本。功能函数
export CATALINA_BASE="/usr/local/tomcat/instance2"
case "$1" in
start)
$CATALINA_HOME/bin/startup.sh
;;
stop)
$CATALINA_HOME/bin/shutdown.sh
;;
restart)
$CATALINA_HOME/bin/shutdown.sh
sleep 5
$CATALINA_HOME/bin/startup.sh
;;
esac
export JAVA_OPTS='-Xms64m -Xmx128m'
vim /usr/local/tomcat/instance1/ins3.sh
#!/bin/bash
#instance3
. /etc/init.d/functions
#functions系统自带脚本。功能函数
export CATALINA_BASE="/usr/local/tomcat/instance3"
case "$1" in
start)
$CATALINA_HOME/bin/startup.sh
;;
stop)
$CATALINA_HOME/bin/shutdown.sh
;;
restart)
$CATALINA_HOME/bin/shutdown.sh
sleep 5
$CATALINA_HOME/bin/startup.sh
;;
esac
export JAVA_OPTS='-Xms64m -Xmx128m'
6. 赋权
chmod +x /usr/local/tomcat/instance1/ins1.sh
chmod +x /usr/local/tomcat/instance2/ins2.sh
chmod +x /usr/local/tomcat/instance3/ins3.sh
7. 启动
mkdir /webapps
cp -r /usr/local/tomcat/webapps/ROOT/ /webapps/
/usr/local/tomcat/instance1/ins1.sh star
/usr/local/tomcat/instance2/ins2.sh start
/usr/local/tomcat/instance3/ins3.sh start
8. 测试
http://192.168.0.104:8091
http://192.168.0.104:8092
http://192.168.0.104:8093
五、Tomcat 集群高可用
tomcat的性能是有极限的,Tomcat理论支持500左右并发,所以接下来我们会聊聊在如何合理使用tomcat上,实际上也就是架构的 层面聊聊如果优化。这里面我们主要讨论两个优化思路:动静分离、Tomcat集群
就需要搭建Tomcat的集群,而目前比较流程的做法就是通过Nginx来实现Tomcat集群的负载均衡。
1、动静分离
Tomcat主要处理servlet、jsp占优势,项目中静态资源(图片、视频、.CSS、.html、.js)访问,耗费请求连接,把静态资源放在Nginx服务器中,静态请求不经过Tomcat,不占并发数。
Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器;同时也是一个IMAP、POP3、SMTP代理服务器;Nginx可以作为一个HTTP服务器进行网站的发布处理,另外Nginx可以作为反向代理进行负载均衡的实现。
优点:
1. 可以高并发连接
官方测试Nginx能够支撑5万并发连接,实际生产环境中可以支撑2~4万并发连接数。
2. 内存消耗少
Nginx+PHP(FastCGI)服务器,在3万并发连接下,开启10个Nginx进程消耗150MB内存,15MB*10=150MB,开启的64个PHP-CGI进程消耗1280内存,20MB*64=1280MB,加上系统自身消耗的内存,总共消耗不到2GB的内存。
3. 成本低廉
购买F5BIG-IP、NetScaler等硬件负载均衡交换机,需要十多万到几十万人民币,而Nginx为开源软件,采用的是2-clause BSD-like协议,可以免费试用,并且可用于商业用途。
4. 配置文件非常简单
网络和程序一样通俗易懂,即使,非专用系统管理员也能看懂。
5. 内置的健康检查功能
如果NginxProxy后端的某台Web服务器宕机了,不会影响前端的访问。
6. 节省带宽
支持GZIP压缩,可以添加浏览器本地缓存的Header头。
7. 稳定性高
用于反向代理,宕机的概率微乎其微。
8. 支持热部署
Nginx支持热部署,它的自动特别容易,并且,几乎可以7天*24小时不间断的运行,即使,运行数个月也不需要重新启动,还能够在不间断服务的情况下,对软件版本进行升级。
Nginx反向代理配置:
2、Tomcat集群配置
1)集群分类
■ 纵向集群 :VERTICAL CLUSTER 多个tomcat部署在同一台服务机上,CPU资源需要抢占,只能对内存进行拓展
■ 横向集群 : HORIZONTAL CLUSTER tomcat和服务机一一对应,即一台服务器上部署一个tomcat。(可做大规模集群)
2)核心概念
■ 负载均衡 :LOAD BALANCE 依据每个节点对应的权重大小分配需要处理的数据
■ 高可用性 : HIGH AVAILABLE 实际运行中只有一台服务器在工作,当其挂掉后其他服务器顶上
Tomcat 官网给出的结构图 :
通过负载均衡,任务分配给集群节点。
3)Tomcat集群配置
1. 配置Apache
修改Apache的httpd.conf 文件,在其后面追加如下内容:
LoadModule jk_module modules/mod_jk-1.2.31-httpd-2.2.3.so
JKWorkersFile conf/workers.properties
JkLogFile logs/mod_jk.log
<VirtualHost *>
DocumentRoot d:/cluster
<Directory "d:/cluster/JMIE">
AllowOverride None
Order allow,deny
Allow from all
</Directory>
ServerAdmin ufida-hf:80
ServerName ufida-hf:80
DirectoryIndex index.html index.htm index.jsp index.action
ErrorLog logs/error_log.txt
CustomLog logs/access_log.txt common
JkMount /*WEB-INF cluster
JkMount /servlet/* cluster
JkMount /*.jsp cluster
JkMount /*.do cluster
JkMount /*.json cluster
JkMount /*.action cluster
</VirtualHost>
备注:这里发布的包名称为JMIE
这个cluster是哪里来的呢?很显然是在worders.properties中进行配置,其内容如下:
worker.list = cluster
#node1
worker.node1.port = 8009
worker.node1.host = localhost
worker.node1.type = ajp13
worker.node1.lbfactor = 1
#node2
worker.node2.port = 9009
worker.node2.host = localhost
worker.node2.type = ajp13
worker.node2.lbfactor = 1
#cluster
worker.cluster.type = lb
worker.cluster.balance_workers = node1,node2
worker.lbcontroller.sticky_session=0 worker.controller.sticky_session_force=false
worker.connection_pool_size=3000
worker.connection_pool_minsize=40
worker.connection_pool_timeout=10000
其中:
worker.node1.host = localhost
worker.节点名称.host 表示的为节点对应的主机名 ,这里为纵向集群,都是在本地配置,如果需要横向集群直接修改对应的host即可。
worker.cluster.type = lb
表示集群方式为负载均衡,其中worder.节点名称.lbfactor 表示节点对应的权重,权重越到处理的TASK越多,这里为1:1 即平均分配。
worker.lbcontroller.sticky_session=0
worker.controller.sticky_session_force=false
保证session可在各节点进行复制,即关闭一台服务器后,我们登录系统的session会被转移到另外一台服务器上,客户端仍能正常操作。
worker.cluster.balance_workers = node1,node2
表示两个节点tomcat对应名称为node1,node2 。
2. 配置TOMCAT
由上可以需要两个tomcat ,解压两个tomcat:
修改tomcat node1 中的conf/server.xml文件:
<Server port="8005" shutdown="SHUTDOWN">
节点2 在修改为8006:
<Connector port="8080" protocol="HTTP/1.1" redirectPort="8443" />
节点2修改为:
<Connector port="9090" protocol="HTTP/1.1" redirectPort="9443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
节点2修改为:
<Connector port="9009" protocol="AJP/1.3" redirectPort="9443" />
以上为设置tomcat在节点的名称,节点2修改为node2 ,随后在其下方加入:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="6">
<Manager className="org.apache.catalina.ha.session.BackupManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"
mapSendOptions="6"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
bind="127.0.0.1"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4001"
selectorTimeout="100"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" timeout="60000"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
节点2修改为4001 。
通过下图:
可知道,为了集群,需要在应用中的web.xml末尾追加一个<distributable/>元素 。
3. Tomcat调优
通过以上的操作,集群环境已经成功搭建了,为了让tomcat能跑的更high,下面来对tomcat进行调优。
① 优化启动参数
在tomcat的bin目录下修改catalina.bat 文件,在该文件头上追加如下信息:
set JAVA_OPTS=-server -Xms1000M -Xmx1000M
其中 server 表示tomcat 允许在生产环境。
-Xms 和 -Xmx 表示最小、最大JVM内存(如果是win32的系统会受到系统内存的限制) 两者设置为一样,可通过如下命令来测试其合适的值:
java -Xmx1200m -version
该值需要手动设置,tomcat的启动参数还有很多,详细可自行查看官方文档:Apache Tomcat 8 (8.0.53) - Documentation Index
② TOMCAT本身优化
将:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
替换为:
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
URIEncoding="UTF-8" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000"
acceptCount="300" maxThreads="300" maxProcessors="1000" minProcessors="5"
useURIValidationHack="false"
compression="on" compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
/>
解释如下:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
URIEncoding="UTF-8" 设置编码 minSpareThreads="25" 最大线程数 maxSpareThreads="75" 最小线程数
enableLookups="false" 关闭DNS查询 disableUploadTimeout="true" connectionTimeout="20000"
acceptCount="300" 线程数达到maxThreads后,后续请求会被放入一个等待队列 maxThreads="300" 最大并发数 maxProcessors="1000" minProcessors="5"
useURIValidationHack="false" 减少对url的不必要的检查
compression="on" 打开压缩功能 compressionMinSize="2048" 启用压缩的输出内容大小 默认为2KB
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" 压缩类型
/>
同样的道理也可以去修改8080端口,这里集群我们只需要用到8009端口,将节点2同样优化后可启动两个Tomcat。
4. 测试集群
将工程分别拷贝到两个TOMCAT中的webapps目录下:
重启apache ,后启动两个tomcat。
六、Tomcat 网络通信模型
1、I/O模型概述
所谓的I/O 就是计算机内存与外部设备之间拷贝数据的过程,其输入输出对象可以是:文件、网络服务、内存等。CPU先把外部设备的数据读到内存里,然后再进行处理,当程序通过 CPU 向外部设备发出一个读指令时,数据从外部设备拷贝到内存往往需要一段时间,这个时候 CPU 没事干了,程序是主动把 CPU 让给别人。
通常情况下IO操作是比较耗时的,所以为了高效的使用硬件,应用程序可以用一个专门线程进行IO操作,而另外一个线程则利用CPU的空闲去做其它计算,这种为提高应用执行效率而采用的IO操作方法即为IO模型。
一个网络 I/O 通信过程,比如网络数据读取,会涉及到两个对象,分别是调用这个 I/O 操作的用户线程和操作系统内核。一个进程的地址空间分为用户空间和内核空间,用户线程不能直接访问内核空间。
网络读取主要有两个步骤:
- 用户线程等待内核将数据从网卡复制到内核空间。
- 内核将数据从内核空间复制到用户空间。
同理,将数据发送到网络也是一样的流程,将数据从用户线程复制到内核空间,内核空间将数据复制到网卡发送。
不同 I/O 模型的区别:实现这两个步骤的方式不一样。
- 对于同步,则指的应用程序调用一个方法是否立马返回,而不需要等待。
- 对于阻塞与非阻塞:主要就是数据从内核复制到用户空间的读写操作是否是阻塞等待的。
I/O 模型主要有 5 种:同步阻塞、同步非阻塞、I/O 多路复用、信号驱动、异步 I/O。是不是很熟悉但是又傻傻分不清他们有何区别?
1)同步阻塞 I/O
用户线程发起read调用的时候,线程就阻塞了,只能让出 CPU,而内核则等待网卡数据到来,并把数据从网卡拷贝到内核空间,当内核把数据拷贝到用户空间,再把刚刚阻塞的读取用户线程唤醒,两个步骤的线程都是阻塞的。
2)同步非阻塞
用户线程一直不停的调用read方法,如果数据还没有复制到内核空间则返回失败,直到数据到达内核空间。用户线程在等待数据从内核空间复制到用户空间的时间里一直是阻塞的,等数据到达用户空间才被唤醒。循环调用read方法的时候不阻塞。
3)I/O 多路复用
用户线程的读取操作被划分为两步:
- 用户线程先发起 select 调用,主要就是询问内核数据转备好了没?当内核把数据准备好了就执行第二步。
- 用户线程再发起 read 调用,在等待内核把数据从内核空间复制到用户空间的时间里,发起 read 线程是阻塞的。
为何叫 I/O 多路复用,核心主要就是:一次 select 调用可以向内核查询多个数据通道(Channel)的状态,因此叫多路复用。
4)异步 I/O
用户线程执行 read 调用的时候会注册一个回调函数, read 调用立即返回,不会阻塞线程,在等待内核将数据准备好以后,再调用刚刚注册的回调函数处理数据,在整个过程中用户线程一直没有阻塞。
5)NIO
Tomcat 的 NioEndpoit 组件实际上就是实现了 I/O 多路复用模型,正式因为这个并发能力才足够优秀。让我们一起窥探下 Tomcat NioEndpoint 的设计原理。
对于 Java 的多路复用器的使用,无非是两步:
- 创建一个 Seletor,在它身上注册各种感兴趣的事件,然后调用 select 方法,等待感兴趣的事情发生。
- 感兴趣的事情发生了,比如可以读了,这时便创建一个新的线程从 Channel 中读数据。
Tomcat 的 NioEndpoint 组件虽然实现比较复杂,但基本原理就是上面两步。我们先来看看它有哪些组件,它一共包含 LimitLatch、Acceptor、Poller、SocketProcessor 和 Executor 共 5 个组件,它们的工作过程如下图所示:
正是由于使用了 I/O 多路复用,Poller 内部本质就是持有 Java Selector 检测 channel 的 I/O 时间,当数据可读写的时候创建 SocketProcessor 任务丢到线程池执行,也就是少量线程监听读写事件,接着专属的线程池执行读写,提高性能。
6)Tomcat 支持的多种I/O模型与应用层协议关系
在 8.0 之前 , Tomcat 默认采用的I/O方式为 BIO , 之后改为 NIO。 无论 NIO、NIO2还是APR, 在性能方面均优于以往的BIO。 如果采用APR, 甚至可以达到 Apache HTTP Server 的影响性能。
APR > NIO2 > NIO > BIO (8版本前有 并且是默认)
Tomcat为了实现支持多种I/O模型和应用层协议,一个容器可能对接多个连接器,就好比一个房间有多个门。但是单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作Service组件。
这里需要注意,Service本身没有做什么重要的事情,只是在连接器和容器外面多包了一层,把它们组装在一起。Tomcat内可能有多个Service,这样的设计也是出于灵活性的考虑。通过在Tomcat中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。
7)BIO、NIO、AIO 适用场景分析
① Java BIO
同步并阻塞,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
② Java NIO
同步非阻塞,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
③ Java AIO (NIO.2)
异步非阻塞,即客户端有连接请求时服务器端就需要启动一个线程,但线程不需要主动去获取消息,而是由OS先完成了数据接收,再通知服务器的线程进行处理。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
2、Tomcat 指定IO模型的配置方式
配置 server.xml 文件当中的 <Connector protocol="HTTP/1.1"> 修改即可,默认配置 8.0 protocol=“HTTP/1.1” 8.0 之前是 BIO, 8.0 之后是 NIO。
BIO:
protocol=“org.apache.coyote.http11.Http11Protocol”
NIO:
protocol=“org.apache.coyote.http11.Http11NioProtocol”
AIO:
protocol=“org.apache.coyote.http11.Http11Nio2Protocol”
APR:
protocol=“org.apache.coyote.http11.Http11AprProtocol”
分别演示在高并发场景下BIO与NIO的线程数的变化:
BIO线程模型:
NIO线程模型:
BIO 配置:
<Connector port="8080" protocol="org.apache.coyote.http11.Http11Protocol"
connectionTimeout="20000"
redirectPort="8443"
compression="on" compressionMinSize="1024"
compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
maxThreads="500" minSpareThreads="1"/>
NIO配置:
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8443"
compression="on" compressionMinSize="1024"
compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
maxThreads="500" minSpareThreads="1"/>
演示数据:
生成环境重要因素:
1. 网络;
2. 程序执行业务用时;
BIO 流程:
Accept 线程组 acceptorThreadCount 默认1个;
exec 线程组 maxThread;
JIoEndpoint;
Acceptor extends Runnable;
SocketProcessor extends Runnable;
NIO 流程:
Accept 线程组 默认两个轮询器;
Poller Selector PollerEvent轮询线程状态;
SocketProcessor;
源代码地址:GitHub - org-hejianhui/bit-bigdata-transmission: bit-bigdata-transmission
结论:
BIO:线程数量 会受到 客户端阻塞、网络延迟、业务处理慢===>线程数会更多。
NIO:线程数量 会受到业务处理慢===>线程数会更多。
七、Tomcat性能调优
1、Tomcat 常用分析工具
1)jps
用来查看所有的jvm进程,包括进程ID,进程启动的路径等。
jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。
ps命令我们经常用到,这个命令主要是用来显示当前系统的进程情况。比如有哪些进程及其 id。jps命令也是一样,它的作用是显示当前系统的java进程情况及其id号。我们可以通过它来查看我们到底启动了几个java进程(因为每一个java程序都会独占一个java虚拟机实例)以及他们的进程号(为下面几个程序做准备),并可通过opt来查看这些进程的详细启动参数。
语法:
jps [-q] [-mlvV] [<hostid>]
-q 安静,只显示pid,不显示class名称,jar文件名和传递给main 方法的参数
-m 输出传递给main 方法的参数,在嵌入式jvm上可能是null
-l (显示完整路径)
-v (显示传递给JVM的命令行参数)
-V (显示通过flag文件传递给JVM的参数)
hostid是主机id,默认localhost
jps
默认显示 进程ID 和 启动类的名称。
4214 Bootstrap
18096 jar
26423 Jps
jps -q
-q 只输出进程ID,而不显示出类的名称
4214
18096
26438
jps -m
-m 可以输出传递给 Java 进程(main 方法)的参数
4214 Bootstrap start
18096 jar
26453 Jps -m
jps -l
-l 可以输出主函数的完整路径(类的全路径)。 -l 可以输出主函数的完整路径(类的全路径)。
4214 org.apache.catalina.startup.Bootstrap
18096 logmon.jar
26468 sun.tools.jps.Jps
jps -V
-V 显示通过flag文件传递给JVM的参
4214 Bootstrap
26512 Jps
2)jinfo
负责观察进程运行环境参数,包括Java System属性和JVM命令行参数。当系统崩溃时,jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息。
语法:
jinfo [ option ] pid
jinfo [ option ] executable core
jinfo [ option ] [server-id@]remote-hostname-or-IP
pid 进程号
executable 产生 core dump 的 java executable
core core file
remote-hostname-or-IP 主机名或ip
server-id 远程主机上的debug server的唯一id
-flags 打印命令行参数
-sysprops 打印系统属性
jinfo 4214
这个命令包含了 JDK 和 JVM 运行起来时的一些属性,4214 JAVA进程号
1 Attaching to process ID 19233, please wait...
2 Debugger attached successfully.
3 Server compiler detected.
4 JVM version is 25.151-b12
5 Java System Properties:
6
7 java.runtime.name = Java(TM) SE Runtime Environment
8 java.vm.version = 25.151-b12
9 sun.boot.library.path = /usr/local/jdk1.8.0_151/jre/lib/amd64
10 shared.loader =
11 java.vendor.url = http://java.oracle.com/
12 java.vm.vendor = Oracle Corporation
13 path.separator = :
14 file.encoding.pkg = sun.io
15 java.vm.name = Java HotSpot(TM) 64-Bit Server VM
16 java.util.logging.config.file = /usr/local/tomcat/instance3/conf/logging.properties
17 tomcat.util.buf.StringCache.byte.enabled = true
18 sun.os.patch.level = unknown
19 sun.java.launcher = SUN_STANDARD
20 user.country = US
21 user.dir = /root
......
3)jstack
用来观察 jvm 中当前所有线程的运行情况和线程当前状态。
语法:
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
core 将被打印信息的core dump文件
remote-hostname-or-IP 远程debug服务的主机名或ip
server-id 唯一id,假如一台主机上多个远程debug服务
pid 需要被打印配置信息的java进程id,可以用jps查询
选项
-F 当’jstack [-l] pid’没有相应的时候强制打印栈信息
-l 长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.
-m 打印java和native c/c++框架的所有栈信息.
jstack -F 18096
18096 JAVA进程号
Attaching to process ID 18096, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.55-b03
Deadlock Detection:
No deadlocks found.
Thread 18105: (state = BLOCKED)
Thread 18104: (state = BLOCKED)
- java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- java.lang.ref.ReferenceQueue.remove(long) @bci=44, line=135 (Interpreted frame)
- java.lang.ref.ReferenceQueue.remove() @bci=2, line=151 (Interpreted frame)
- java.lang.ref.Finalizer$FinalizerThread.run() @bci=16, line=189 (Interpreted frame)
Thread 18103: (state = BLOCKED)
- java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- java.lang.Object.wait() @bci=2, line=503 (Interpreted frame)
- java.lang.ref.Reference$ReferenceHandler.run() @bci=46, line=133 (Interpreted frame)
4)jstat
用于输出指定 java 进程的统计信息。
利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对进程的classloader,compiler情况;可以用来监视VM内存内的各种堆和非堆的大小及其内存使用量,以及加载类的数量。
语法:
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
class:统计classloader的行为
compiler:统计hotspot just-in-time编译器的行为
gc:统计gc行为
gccapacity:统计堆中代的容量、空间
gccause:垃圾收集统计,包括最近引用垃圾收集的事件,基本同gcutil,比gcutil多了两列
gcnew:统计新生代的行为
gcnewcapacity:统计新生代的大小和空间
gcold:统计旧生代的行为
gcoldcapacity:统计旧生代的大小和空间
gcpermcapacity:统计永久代的大小和空间
gcutil:垃圾收集统计
printcompilation:hotspot编译方法统计
-h n 每n个样本,显示header一次
-t n 在第一列显示时间戳列,时间戳时从jvm启动开始计算
<vmid> 就是进程号
<interval> interval是监控时间间隔,单位为微妙,不提供就意味着单次输出
<count> count是最大输出次数,不提供且监控时间间隔有值的话, 就无限打印
jstat -class 4214 2000 10
(每隔2秒监控一次,一共做10次)
Loaded Bytes Unloaded Bytes Time
9197 18418. 0 0 0.0 12.49
9197 18418. 0 0 0.0 12.49
列名介绍:
Column Description
Loaded 被读入类的数量
Bytes 被读入的字节数(K)
Unloaded 被卸载类的数量
Bytes 被卸载的字节数(K)
Time 花费在load和unload类的时间
5)jmap
用来监视进程运行中的jvm物理内存的占用情况,该进程内存内,所有对象的情况,例如产生了哪些对象,对象数量。当系统崩溃时,jmap 可以从core文件或进程中获得内存的具体匹配情况,包括Heap size, Perm size等。
语法:
jmap [option] <pid>
jmap [option] <executable <core>
jmap [option] [server_id@]<remote server IP or hostname>
-dump:format=b,file=<filename> pid # dump堆到文件,format指定输出格式,live指明是活着的对象,file指定文件名
-finalizerinfo # 打印等待回收对象的信息
-heap # 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况,可以用此来判断内存目前的使用情况以及垃圾回收情况
-histo[:live] # 打印堆的对象统计,包括对象数、内存大小等等 (因为在dump:live前会进行full gc,因此不加live的堆大小要大于加live堆的大小 )
-permstat # 打印classload类装载器和 jvm heap长久层的信息. 包含包括每个装载器的名字,活跃,地址,父装载器,和其总共加载的类大小。另外,内部String的数量和占用内存数也会打印出来.
-F # 强制,强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.
-J # 传递参数给jmap启动的jvm. ,如:-J-Xms256m
# jmap -heap 4214
Attaching to process ID 4214, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.55-b03
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration: # 堆配置情况
MinHeapFreeRatio = 40 # 最小堆的使用比例
MaxHeapFreeRatio = 70 # 最大堆的可用比例
MaxHeapSize = 2684354560 (2560.0MB) # 最大堆空间大小
NewSize = 713031680 (680.0MB) # 新生代分配大小
MaxNewSize = 713031680 (680.0MB) # 最大可用新生代分配大小
OldSize = 5439488 (5.1875MB) # 老年代大小
NewRatio = 2 # 新生代比例
SurvivorRatio = 8 # 新生代与suvivor的比例
PermSize = 251658240 (240.0MB) # perm区大小
MaxPermSize = 251658240 (240.0MB) # 最大可分配perm区大小
G1HeapRegionSize = 0 (0.0MB) # G1堆区大小
Heap Usage: # 堆使用情况
New Generation (Eden + 1 Survivor Space): # 新生代(伊甸区 + survior空间)
capacity = 641728512 (612.0MB) # 伊甸区容量
used = 507109064 (483.6168899536133MB) # 已经使用大小
free = 134619448 (128.38311004638672MB) # 剩余容量
79.0223676394793% used # 使用比例
Eden Space: # 伊甸区
capacity = 570425344 (544.0MB) # 伊甸区容量
used = 503156488 (479.84741973876953MB) # 伊甸区使用
free = 67268856 (64.15258026123047MB) # 伊甸区当前剩余容量
88.2072462755091% used # 伊甸区使用情况
From Space: # survior1区
capacity = 71303168 (68.0MB) # survior1区容量
used = 3952576 (3.76947021484375MB) # surviror1区已使用情况
free = 67350592 (64.23052978515625MB) # surviror1区剩余容量
5.543338551240809% used # survior1区使用比例
To Space: # survior2 区
capacity = 71303168 (68.0MB) # survior2区容量
used = 0 (0.0MB) # survior2区已使用情况
free = 71303168 (68.0MB) # survior2区剩余容量
0.0% used # survior2区使用比例
concurrent mark-sweep generation: # 老生代使用情况
capacity = 1971322880 (1880.0MB) # 老生代容量
used = 1514740296 (1444.5689163208008MB) # 老生代已使用容量
free = 456582584 (435.4310836791992MB) # 老生代剩余容量
76.83877214472345% used # 老生代使用比例
Perm Generation: # perm区使用情况
capacity = 251658240 (240.0MB) # perm区容量
used = 57814400 (55.1361083984375MB) # perm区已使用容量
free = 193843840 (184.8638916015625MB) # perm区剩余容量
22.973378499348957% used # perm区使用比例
28645 interned Strings occupying 3168232 bytes.
6)VirtualVM
VisualVM 是一款免费的性能分析工具。它通过 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多种方式从程序运行时获得实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度。
线程转储的生成与分析
VisualVM 能够对正在运行的本地应用程序生成线程转储,把活动线程的堆栈踪迹打印出来,帮助我们有效了解线程运行的情况,诊断死锁、应用程序瘫痪等问题。
1. 监视
监视是一种用来查看应用程序运行时行为的一般方法。通常会有多个视图(View)分别实时地显示 CPU 使用情况、内存使用情况、线程状态以及其他一些有用的信息,以便用户能很快地发现问题的关键所在。
2. 转储
性能分析工具从内存中获得当前状态数据并存储到文件用于静态的性能分析。Java 程序是通过在启动 Java 程序时添加适当的条件参数来触发转储操作的,它包括以下三种:
系统转储:JVM 生成的本地系统的转储,又称作核心转储。一般的,系统转储数据量大,需要平台相关的工具去分析,如 Windows 上的 windbg 和 Linux 上的 gdb。
Java 转储:JVM 内部生成的格式化后的数据,包括线程信息,类的加载信息以及堆的统计数据。通常也用于检测死锁。
堆转储:JVM 将所有对象的堆内容存储到文件
3. 快照
应用程序启动后,性能分析工具开始收集各种运行时数据,其中一些数据直接显示在监视视图中,而另外大部分数据被保存在内部,直到用户要求获取快照,基于这些保存的数据的统计信息才被显示出来。快照包含了应用程序在一段时间内的执行信息,通常有 CPU 快照和内存快照两种类型。
CPU 快照:主要包含了应用程序中函数的调用关系及运行时间,这些信息通常可以在 CPU 快照视图中进行查看。
内存快照:主要包含了内存的分配和使用情况、载入的所有类、存在的对象信息及对象间的引用关系等。这些信息通常可以在内存快照视图中进行查看。
使用VirtualVM进行性能分析
性能分析是通过收集程序运行时的执行数据来帮助开发人员定位程序需要被优化的部分,从而提高程序的运行速度或是内存使用效率,主要有以下三个方面:
CPU 性能分析:CPU 性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。通常有 CPU 监视和 CPU 快照两种方式来显示 CPU 性能分析结果。
内存性能分析:内存性能分析的主要目的是通过统计内存使用情况检测可能存在的内存泄露问题及确定优化内存使用的方向。通常有内存监视和内存快照两种方式来显示内存性能分析结果。
线程性能分析:线程性能分析主要用于在多线程应用程序中确定内存的问题所在。一般包括线程的状态变化情况,死锁情况和某个线程在线程生命期内状态的分布情况等。
图例:
内存分析:
CPU分析:
线程分析:
快照功能:
转储功能:
2、Tomcat 性能调优策略
1)性能优化的三个指标
降低响应时间;
提高系统吞吐量(QPS) : QPS (每秒请求量) T(事务) PS (每秒处理事务的数量);
提高服务的可用性;
2)性能优化的原则
具体情况具体分析;
积少成多;
3)性能分析工具
JConsole jvm性能监控平台: 主要监控 内存和CPU的情况;
JMeter 分布式性能测试工具: 响应时间、吞吐量、错误率;
4)性能优化测试指标
正确率、CPU占有率、QPS、JVM。
3、Tomcat 性能测试
1)JMeter 安装及配置
拷贝资料中的jmeter压缩包,到你要安装的目录中 解压(不要有中文目录哦)
配置jmeter环境变量 如:我的安装位置 D:\tools\apache-jmeter-5.1.1
1. 配置 JMETER_HOME,变量值 D:\tools\apache-jmeter-5.1.1
2. 配置CLASSPATH (没有就新增,有就在后面添加):
%JMETER_HOME%\lib\ext\ApacheJMeter_core.jar;
%JMETER_HOME%\lib\jorphan.jar;
%JMETER_HOME%\lib\logkit-2.0.jar;
3. 配置Path (没有就新增,有就在后面添加):
%JMETER_HOME%/bin
2)JMeter 启动
到安装目录的bin目录下,双击jmeter.bat (windows系统)。
双击后等待一会,弹出如下图片代表启动成功:
默认的语言设置是英文 可以通过:options --> choose language --> chinese simple 设置中文简体。
3)使用JMeter进行Tomcat压力测试
1. TestPland右键添加-线程-线程组: 作用:创建出大量的线程,每一个线程都会访问Tomcat,执行很多次请求得到综合结果。
2. 设置线程数。
3. 配置取样器:线程组右键-取样器-选择要模拟的协议请求方式。
4. 统计结果:HTTP请求右键-添加监听器-聚合报告和察看结果树,设置好后保存。
察看结果树:
5. 聚合报告:跟据指标不断优化结果数据。
6. JMeter的测试结果分析
Label----每个请求的名称,比如HTTP请求等
#Samples----发给服务器的请求数量
Average----单个请求的平均响应时间 毫秒ms
Median----50%请求的响应时间 毫秒ms
90%Line----90%请求响应时间 毫秒ms
95%Line----95%请求响应时间 毫秒ms
99%Line----99%请求的响应时间 毫秒ms
Min----最小的响应时间 毫秒ms
Max----最大的响应时间 毫秒ms
Error%----错误率=错误的请求的数量/请求的总数
Throughput----吞吐量,默认情况下表示每秒完成的请求数(Request per Second),当使用了 Transaction Controller 时,也可以表示类似 LoadRunner 的 Transaction per Second 数。
Received KB/sec----每秒从服务器端接收到的数据量
Sent KB/sec----每秒从客户端发送的请求的数量
4、Tomcat 性能优化
1)Tomcat配置调优
1. 禁用AJP连接
默认状态下,Tomcat会启动AJP服务,占用8009端口。
AJP是什么?有什么作用呢?
AJP(Apache JServer Protocol) 是为Tomcat与HTTP服务器之间通信而定制的协议,能提供较高的通信速度和效率。
Tomcat虽然是一个javaWeb服务器,可以对静态资源进行解析,但它最主要的作用是提供Servlet 和 Jsp容器,对静态资源的解析肯定不如一些专业的Http服务器 如:apache、nginx 。所以通常生产环境会把Tomcat和其他http服务器搭配使用, 所有请求都访问http服务器,如果访问的资源是Servlet或Jsp则http服务器将请求交由Tomcat处理,如果静态资源 http服务器直接处理, ajp就是apache服务器和tomcat服务器通信的主要协议,nginx 和 浏览器是不支持这个协议的,所以如果你不使用Tomcat和Apache服务器整合的话,这个AJP协议服务是没必要开启的。
ajp就是下面这行代码来配置,主要注释它就可以禁用AJP:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
2. Connector 连接器的选择
Tomcat 连接器的三种方式: bio、nio 和 apr,三种方式性能差别很大,apr 的性能最优, bio 的性能最差。而 Tomcat 7 使用的 Connector 默认就启用的 Apr 协议,但需要系统安装 Apr 库,否则就会使用 bio 方式。
- BIO:
一个线程处理一个请求。缺点:并发量高时,线程数较多,浪费资源。
Tomcat7或以下,在Linux系统中默认使用这种方式。
- NIO:
利用Java的异步IO处理,可以通过少量的线程处理大量的请求。
Tomcat8在Linux系统中默认使用这种方式。
Tomcat7必须修改Connector配置来启动:
- APR:
即Apache Portable Runtime,从操作系统层面解决io阻塞问题。
Tomcat7或Tomcat8在Win7或以上的系统中启动默认使用这种方式。
Linux如果安装了apr和native,Tomcat直接启动就支持apr。
tomcat8 提供了nio2,效率要比nio快些,所以如果是tomcat8之前的版本推荐使用nio ,如果是tomcat8及之后的版本使用nio2。
APR( Apache Portable Runtime) 提供卓越的伸缩性,更强的性能以及跟原生服务器技术更好的集成,如果使用APR要保证:
1. 配置文件中引入了APR的监听;
2. 服务器上安装APR的相关软件;
linux配置APR
安装依赖:
yum install apr-devel
yum install openssl-devel
yum install gcc
yum install make
安装APR包:
进入 apache-tomcat-8.5.39/bin 执行命令
tar -xzvf tomcat-native.tar.gz
bin目录下有这个文件夹:
输入命令:
cd tomcat-native-1.2.21-src/native/
进入到native文件夹下,执行命令:
./configure && make && make install
安装后会出现这个界面,表示安装成功,并给出了安装后的目录位置。
通过上图我们可以看到 我们还需要配置两个环境变量,输入一下命令使环境变量生效。
source /etc/profile
修改server.xml 配置:
在这段注释中,已经告诉我们如何设置ARP, 按照配置来修改Connector设置。
启动tomcat查看日志,进入logs目录,执行:
tail -f -n 200 catalina.out
<Connector executor="tomcatThreadPool"
port="8080"
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
redirectPort="8443"/>
Connector参数解释:
URIEncoding:指定 Tomcat 容器的 URL 编码格式,语言编码格式这块倒不如其它 WEB 服务器软件配置方便,需要分别指定。
onnnectionTimeout: 网络连接超时,单位:毫秒,设置为 0 表示永不超时,这样设置有隐患的。通常可设置为 30000 毫秒(30秒),可根据检测实际情况,适当修改。设置连接超时时间,当后台出现问题,连接可以及时断掉。
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:表示压缩响应的最小值,只有当响应报文大小大于这个值的时候才会对报文进行压缩,如果开启了压缩功能,默认值就是2048KB。
compressableMimeType:压缩类型,指定对哪些类型的文件进行数据压缩。
noCompressionUserAgents="gozilla, traviata": 对于以下的浏览器,不启用压缩。
参考配置:
连接器的具体参考配置。
<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" />
3. Executor线程池调整
默认配置下,Tomcat 会为每个连接器创建一个绑定的线程池(最大线程数 200),服务启动时,默认创建了 5 个空闲线程随时等待用户请求
默认情况:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/>
修改连接池:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" minSpareThreads="100" maxSpareThreads="50" maxIdleTime="60000"/>
机器比较好情况:最小线程数不超过<100,最大线程数不超过<500。
然后,修改<Connector …>节点,增加 executor 属性,调整为:
<Connector executor="tomcatThreadPool"
port="8080"
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
redirectPort="8443"/>
Executor参数解释:
maxThreads,最大并发数,默认设置 200,一般建议在 500 ~ 800,根据硬件设施和业务来判断
minSpareThreads,Tomcat 初始化时创建的线程数,默认设置 25
prestartminSpareThreads,在 Tomcat 初始化的时候就初始化 minSpareThreads 的参数值,如果不等于 true,minSpareThreads 的值就没啥效果了
maxQueueSize,最大的等待队列数,超过则拒绝请求
2)JVM参数调优
Tomcat是一款Java应用,那么JVM的配置便与其运行性能密切相关,而JVM优化的重点则集中在内存分配和GC策略的调整上,因为内存会直接影响服务的运行效率和吞吐量,JVM垃圾回收机制则会不同程度地导致程序运行中断。
可以根据应用程序的特点,选择不同的垃圾回收策略,调整JVM垃圾回收策略,可以极大减少垃圾回收次数,提升垃圾回收效率,改善程序运行性能。
1. JVM内存参数
参数 | 参数作用 | 优化建议 |
---|---|---|
-server | 启动Server,以服务端模式运行 | 服务端模式建议开启 |
-Xms | 最小堆内存 | 建议与-Xmx设置相同 |
-Xmx | 最大堆内存 | 建议设置为可用内存的80% |
-XX:MetaspaceSize | 元空间初始值 | |
-XX:MaxMetaspaceSize | 元空间最大内存 | 默认无限 |
-XX:NewSize | 新生代初始值 | |
-XX:MaxNewSize | 新生代最大内存 | 默认16M |
-XX:NewRatio | 年轻代和老年代大小比值,取值为整数,默认为2 | 不建议修改 |
-XX:SurvivorRatio | Eden区与Survivor幸存者区大小的比值,取值为整数,默认为8 | 不建议修改 |
-Xms:Java虚拟机初始化时堆的最小内存,一般与 Xmx配置为相同值,这样的好处是GC不必再为扩展内存空间而消耗性能;
可以利用JDK自带的工具进行验证,这些工具都在/bin目录下:
1)jps:用来显示本地的java进程,以及进程号,进程启动的路径等。
2)jmap:观察运行中的JVM 物理内存的占用情况,包括Heap size , Perm size等。 进入命令行模式后,进入JAVA_HOME/bin目录下,然后输入jps命令。
查看当前服务器的可用内存:
free -m
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:SurvivorRatio=8"
2. GC策略
JVM垃圾回收性能有以下两个主要的指标:
- 吞吐量:工作时间(排除GC时间)占总时间的百分比, 工作时间并不仅是程序运行的时间,还包含内存分配时间。
- 暂停时间:测试时间段内,由垃圾回收导致的应用程序停止响应次数/时间。
不同的应用程序, 对于垃圾回收会有不同的需求。 JVM 会根据运行的平台、服务器资源配置情况选择合适的垃圾收集器、堆内存大小及运行时编译器。
如无法满足需求, 参考以下准则:
A. 程序数据量较小,选择串行收集器。
B. 应用运行在单核处理器上且没有暂停时间要求, 可交由JVM自行选择或选择串行收集器。
C. 如果考虑应用程序的峰值性能, 没有暂停时间要求, 可以选择并行收集器。
D. 如果应用程序的响应时间比整体吞吐量更重要, 可以选择并发收集器。
查看Tomcat中的默认的垃圾收集器:
① 在tomcat/bin/catalina.sh的配置中, 加入如下配置。
JAVA_OPTS=" -Djava.rmi.server.hostname=192.168.18.129 -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.rmi.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
② 打开 jconsole , 查看远程的tomcat的概要信息。
连接远程tomcat:
GC参数:
参数 | 描述 |
---|---|
-XX:+UseSerialGC | 启用串行收集器 |
-XX:+UseParallelGC | 启用并行垃圾收集器,配置了该选项,那么 -XX:+UseParallelOldGC默认启用 |
-XX:+UseParallelOldGC | FullGC 采用并行收集,默认禁用。如果设置了 -XX:+UseParallelGC则自动启用 |
-XX:+UseParNewGC | 年轻代采用并行收集器,如果设置了 -XX:+UseConcMarkSweepGC选项,自动启用 |
-XX:+ParallelGCThreads | 年轻代及老年代垃圾回收使用的线程数。默认值依赖于JVM使用的CPU个数 |
-XX:+UseConcMarkSweepGC | 并发垃圾收集器 对于老年代,启用CMS垃圾收集器。 当并行收集器无法满足应用的延迟需求是,推荐使用CMS或G1收集器。 启用该选项后, -XX:+UseParNewGC 自动启用。 |
-XX:+UseG1GC | 启用G1收集器。 G1是服务器类型的收集器, 用于多核、大内存的机器。它在保持高吞吐量的情况下,高概率满足GC暂停时间的目标。 |
我们也可以在测试的时候,将JVM参数调整之后,将GC的信息打印出来,便于为我们进行参数调整提供依据,具体参数如下:
选项 | 描述 |
---|---|
-XX:+PrintGC | 打印每次GC的信息 |
-XX:+PrintGCApplicationConcurrentTime | 打印最后一次暂停之后所经过的时间, 即响应并发执行的时间 |
-XX:+PrintGCApplicationStoppedTime | 打印GC时应用暂停时间 |
-XX:+PrintGCDateStamps | 打印每次GC的日期戳 |
-XX:+PrintGCDetails | 打印每次GC的详细信息 |
-XX:+PrintGCTaskTimeStamps | 打印每个GC工作线程任务的时间戳 |
-XX:+PrintGCTimeStamps | 打印每次GC的时间戳 |
在bin/catalina.sh的脚本中 , 追加如下配置 :
JAVA_OPTS="-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails"
64位系统 centos 6.4 内存8G服务器 :
-server
-Xms6000M
-Xmx6000M
-Xmn500M
-XX:MetaspaceSize=500M
-XX:MaxMetaspaceSize=500M
-XX:SurvivorRatio=65536
-XX:MaxTenuringThreshold=0
-Xnoclassgc
-XX:+DisableExplicitGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:CMSInitiatingOccupancyFraction=80
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log