【Java高级工程师蜕变之路】024 Tomcat源码剖析与调优
手写mini版Tomcat
Minicat要做的事情:
作为一个服务器软件提供服务的,也即我们可以通过浏览器客户端发送http请求, Minicat可以接收到请求进行处理,处理之后的结果可以返回浏览器客户端。
1)提供服务,接收请求(Socket通信) 2)请求信息封装成Request对象(Response对象) 3)客户端请求资源,资源分为静态资源(html)和动态资源(Servlet) 4)资源返回给客户端浏览器
1.0
V1.0需求:浏览器请求http://localhost:8080 ,返回一个固定的字符串到⻚面"Hello Minicat!"
2.0
V2.0需求:封装Request和Response对象,返回html静态资源文件
3.0
V3.0需求:可以请求动态资源(Servlet)
4.0
V4.0需求:在已有Minicat基础上进一步扩展,模拟出webapps部署效果 磁盘上放置一个webapps目录,webapps中可以有多个项目,例如demo1、demo2、demo3... 每个项目中含有servlet,可以根据请求url定位对应servlet进一步处理。
最终代码
完成4.0版本之后,最终代码如下:
Bootstrap启动类
/**
* Minicat的主类
*/
public class Bootstrap {
/**
* 定义socket监听的端口号
*/
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
* Minicat启动需要初始化展开的一些操作
*/
public void start() throws Exception {
// 加载解析相关的配置,web.xml
loadServlet();
// 解析server.xml,读取webapps路径
// 遍历webapps,每个应用通过单独设置类加载器(防止默认的类加载机制导致不同应用的相同class问题)
// 读取web.xml的servlet配置,生成servlet保存到servletMap
// 保存的key需要加上应用的前缀
loadWebapps();
System.out.println("全部加载完成,servletMap:" + servletMap);
// 定义一个线程池
int corePoolSize = 10;
int maximumPoolSize = 50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
/*
完成Minicat 1.0版本
需求:浏览器请求http://localhost:8080,返回一个固定的字符串到页面"Hello Minicat!"
*/
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("=====>>>Minicat start on port:" + port);
/*while(true) {
Socket socket = serverSocket.accept();
// 有了socket,接收到请求,获取输出流
OutputStream outputStream = socket.getOutputStream();
String data = "Hello Minicat!";
String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;
outputStream.write(responseText.getBytes());
socket.close();
}*/
/**
* 完成Minicat 2.0版本
* 需求:封装Request和Response对象,返回html静态资源文件
*/
/*while(true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
response.outputHtml(request.getUrl());
socket.close();
}*/
/**
* 完成Minicat 3.0版本
* 需求:可以请求动态资源(Servlet)
*/
/*while(true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
// 静态资源处理
if(servletMap.get(request.getUrl()) == null) {
response.outputHtml(request.getUrl());
}else{
// 动态资源servlet请求
HttpServlet httpServlet = servletMap.get(request.getUrl());
httpServlet.service(request,response);
}
socket.close();
}
*/
/*
多线程改造(不使用线程池)
*/
/*while(true) {
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
requestProcessor.start();
}*/
// System.out.println("=========>>>>>>使用线程池进行多线程改造");
/*
多线程改造(使用线程池)
*/
/*
while(true) {
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
//requestProcessor.start();
threadPoolExecutor.execute(requestProcessor);
}
*/
/**
* 完成Minicat 4.0版本
* 需求:实现webapps部署,并支持多项目部署
*/
while (true) {
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
threadPoolExecutor.execute(requestProcessor);
}
}
/**
* 加载解析server.xml,初始化webapps里面的servlet
*/
private void loadWebapps() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element element = document.getRootElement();
// 为了简单,暂不考虑多个host的情况,以单个host为例
List<Element> hosts = element.selectNodes("//Host");
// 未找到主机,不处理
if (null == hosts || hosts.size() == 0) {
return;
}
Element host = hosts.get(0);
String appBase = host.attributeValue("appBase");
// 找到appBase
String classPath = this.getClass().getResource(".").getFile();
String absoluteAppBase = Paths.get(classPath).getParent().getParent().getParent().toAbsolutePath().toString() + "/" + appBase;
File file = new File(absoluteAppBase);
if (!file.exists()) {
System.out.println("webapps目录不存在,将不会加载任何应用");
return;
}
System.out.println("准备从下面的目录加载webapps:" + absoluteAppBase + "\n");
File[] dirs = file.listFiles();
for (File dir : dirs) {
if (!dir.isDirectory()) {
continue;
}
// 遍历每个应用
String appPath = "/" + dir.getName();
List<Element> contextList = host.elements();
for (Element context : contextList) {
String docBase = context.attributeValue("docBase");
if (!appPath.equals(docBase)) {
continue;
}
String appPrefix = context.attributeValue("path");
String absAppPath = absoluteAppBase + docBase;
System.out.println("开始加载应用:" + appPrefix + ",应用路径:" + absAppPath);
// 加载各个应用的servlet
loadWebappsServlet(appPrefix, absAppPath);
}
}
System.out.println("加载webapps的servlet完成");
} catch (DocumentException e) {
e.printStackTrace();
}
}
private Map<String, HttpServlet> servletMap = new HashMap<String, HttpServlet>();
/**
* 加载解析web.xml,初始化Servlet
*/
private void loadServlet() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// <servlet-name>lagou</servlet-name>
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// <servlet-class>server.LagouServlet</servlet-class>
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletclassElement.getStringValue();
// 根据servlet-name的值找到url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
// /lagou
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
}
System.out.println("加载servlet完成");
} catch (DocumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void loadWebappsServlet(String appPrefix, String absAppPath) {
InputStream resourceAsStream = null;
try {
resourceAsStream = new FileInputStream(absAppPath + "/web.xml");
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// <servlet-name>lagou</servlet-name>
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// <servlet-class>server.LagouServlet</servlet-class>
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletclassElement.getStringValue();
// 根据servlet-name的值找到url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
// /lagou
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
// 使用自定义的类加载器加载
MyClassLoader loader = new MyClassLoader(absAppPath, servletClass);
Class<?> aClass = loader.loadClass(servletClass);
// 注意:下面的会从默认的类加载器加载
// Class<?> aClass = Class.forName(servletClass);
// 加载失败不添加
if (null == aClass) {
continue;
}
servletMap.put(appPrefix + urlPattern, (HttpServlet) aClass.newInstance());
}
System.out.println("加载应用" + appPrefix + "的servlet完成\n");
} catch (DocumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* Minicat 的程序启动入口
*
* @param args
*/
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
// 启动Minicat
bootstrap.start();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义的ClassLoader
/**
* 自定义类加载器,主要用于应用的servlet加载
*
* @name: MyClassLoader
* @author: terwer
* @date: 2022-01-18 10:19
**/
public class MyClassLoader extends ClassLoader {
private String absAppPath;
private String servletClass;
public MyClassLoader(String absAppPath, String servletClass) {
this.absAppPath = absAppPath;
this.servletClass = servletClass;
}
/**
* 通过指定全路径记载class文件
*
* @param name class文件路径(包名+class文件名)
* @return Class<?>
* @throws ClassNotFoundException
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 这里要注意:如果不是servlet,需要使用双亲委派
// 因为应用里面会使用Minicat里面定义的类
// 而这些类没必要拷贝一份到应用里面
// 只有servlet才去跳过双亲委派
if (!name.equals(this.servletClass)) {
System.out.println("非servlet,不使用自定义类加载器:" + name);
Class<?> clazz = this.getClass().getClassLoader().loadClass(name);
return clazz;
}
String filePath = absAppPath + "/" + name.replaceAll("\\.", "/") + ".class";
String classFullPath = "file://" + filePath;
System.out.println("MyClassLoader开始加载:" + classFullPath);
byte[] classBytes = null;
Path path = null;
try {
path = Paths.get(new URI(classFullPath));
classBytes = Files.readAllBytes(path);
} catch (URISyntaxException | IOException e) {
e.printStackTrace();
}
Class<?> clazz = defineClass(name, classBytes, 0, classBytes.length);
return clazz;
}
}
http协议工具类
/**
* http协议工具类,主要是提供响应头信息,这里我们只提供200和404的情况
*/
public class HttpProtocolUtil {
/**
* 为响应码200提供请求头信息
* @return
*/
public static String getHttpHeader200(long contentLength) {
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html \n" +
"Content-Length: " + contentLength + " \n" +
"\r\n";
}
/**
* 为响应码404提供请求头信息(此处也包含了数据内容)
* @return
*/
public static String getHttpHeader404() {
String str404 = "<h1>404 not found</h1>";
return "HTTP/1.1 404 NOT Found \n" +
"Content-Type: text/html \n" +
"Content-Length: " + str404.getBytes().length + " \n" +
"\r\n" + str404;
}
}
Request封装类
/**
* 把请求信息封装为Request对象(根据InputSteam输入流封装)
*/
public class Request {
private String method; // 请求方式,比如GET/POST
private String url; // 例如 /,/index.html
private InputStream inputStream; // 输入流,其他属性从输入流中解析出来
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public Request() {
}
// 构造器,输入流传入
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
// 从输入流中获取请求信息
int count = 0;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String inputStr = new String(bytes);
// 获取第一行请求头信息
String firstLineStr = inputStr.split("\\n")[0]; // GET / HTTP/1.1
String[] strings = firstLineStr.split(" ");
this.method = strings[0];
this.url = strings[1];
System.out.println("=====>>method:" + method);
System.out.println("=====>>url:" + url);
}
}
Response封装类
/**
* 封装Response对象,需要依赖于OutputStream
*
* 该对象需要提供核心方法,输出html
*/
public class Response {
private OutputStream outputStream;
public Response() {
}
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
// 使用输出流输出指定字符串
public void output(String content) throws IOException {
outputStream.write(content.getBytes());
}
/**
*
* @param path url,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过
* 输出流输出
* /-----> classes
*/
public void outputHtml(String path) throws IOException {
// 获取静态资源文件的绝对路径
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
// 输入静态资源文件
File file = new File(absoluteResourcePath);
if(file.exists() && file.isFile()) {
// 读取静态资源文件,输出静态资源
StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
}else{
// 输出404
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
静态资源处理工具类
public class StaticResourceUtil {
/**
* 获取静态资源文件的绝对路径
* @param path
* @return
*/
public static String getAbsolutePath(String path) {
String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
return absolutePath.replaceAll("\\\\","/") + path;
}
/**
* 读取静态资源文件输入流,通过输出流输出
*/
public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
int count = 0;
while(count == 0) {
count = inputStream.available();
}
int resourceSize = count;
// 输出http请求头,然后再输出具体内容
outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
// 读取内容输出
long written = 0 ;// 已经读取的内容长度
int byteSize = 1024; // 计划每次缓冲的长度
byte[] bytes = new byte[byteSize];
while(written < resourceSize) {
if(written + byteSize > resourceSize) { // 说明剩余未读取大小不足一个1024长度,那就按真实长度处理
byteSize = (int) (resourceSize - written); // 剩余的文件内容长度
bytes = new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
written+=byteSize;
}
}
}
动态资源请求
Servlet
public interface Servlet {
void init() throws Exception;
void destory() throws Exception;
void service(Request request,Response response) throws Exception;
}
HttpServlet
public abstract class HttpServlet implements Servlet{
public abstract void doGet(Request request,Response response);
public abstract void doPost(Request request,Response response);
@Override
public void service(Request request, Response response) throws Exception {
if("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request,response);
}else{
doPost(request,response);
}
}
}
业务类LagouServlet
public class LagouServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) {
// try {
// Thread.sleep(100000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
String content = "<h1>LagouServlet get</h1>";
try {
response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(Request request, Response response) {
String content = "<h1>LagouServlet post</h1>";
try {
response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void init() throws Exception {
}
@Override
public void destory() throws Exception {
}
}
多线程版RequestProcessor
public class RequestProcessor extends Thread {
private Socket socket;
private Map<String,HttpServlet> servletMap;
public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
this.socket = socket;
this.servletMap = servletMap;
}
@Override
public void run() {
try{
InputStream inputStream = socket.getInputStream();
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
// 静态资源处理
if(servletMap.get(request.getUrl()) == null) {
response.outputHtml(request.getUrl());
}else{
// 动态资源servlet请求
HttpServlet httpServlet = servletMap.get(request.getUrl());
httpServlet.service(request,response);
}
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
完整代码地址
https://gitee.com/youweics/Minicat
Tomcat源码剖析
Tomcat源码构建
下载源码
https://tomcat.apache.org/download-80.cgi
https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.73/src/apache-tomcat-8.5.73-src.tar.gz
源码导入准备
- 解压源码,在 apache-tomcat-8.5.73-src 目录创建一个 pom.xml 文件,内容如下
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVer
<!--引入编译插件--> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
<!--tomcat 依赖的基础包-->
<dependencies>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>
-
在 apache-tomcat-8.5.73-src 目录中创建 source 文件夹
-
将 conf、webapps 目录移动到刚刚创建的 source 文件夹中
将源码工程导入到 IDEA 中
给 tomcat 的源码程序启动类 Bootstrap 配置 VM 参数,因为 tomcat 源码运行也需要加载配置文 件等。
-Dcatalina.base=/Users/terwer/Documents/code/tomcat8/apache-tomcat-8.5.73-src/source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=/Users/terwer/Documents/code/tomcat8/apache-tomcat-8.5.73-src/source/conf/logging.properties
解决 JSP 编译错误
需要在tomcat的源码ContextConfig类中 的configureStart方法中增加一行代码将 Jsp 引擎初始化
重启Tomcat。
Tomcat核心流程源码剖析
生命周期
Tomcat中的各容器组件都会涉及创建、销毁等,因此设计了生命周期接口Lifecycle进行统一规范,各容 器组件实现该接口。
Lifecycle生命周期接口继承体系示意图
核心源码
主要关注Tomcat启动流程和Tomcat请求处理流程
Tomcat启动流程
Tomcat请求处理流程
请求处理流程分析
请求处理流程示意图
Tomcat Mapper组件的体系结构
Tomcat类加载机制剖析
Java类(.java)—> 字节码文件(.class) —> 字节码文件需要被加载到jvm内存当中(这个过程就是一个 类加载的过程)
类加载器(ClassLoader,说白了也是一个类,jvm启动的时候先把类加载器读取到内存当中去,其他的 类(比如各种jar中的字节码文件,自己开发的代码编译之后的.class文件等等))
要熟悉Tomcat的类加载机制,首先的熟悉JVM的类加载机制,因为Tomcat是运行在JVM之上的
JVM类加载机制
JVM 的类加载机制中有一个非常重要的⻆色叫做类加载器(ClassLoader),类加载器有自己的体系, Jvm内置了几种类加载器,包括:引导类加载器、扩展类加载器、系统类加载器,他们之间形成父子关 系,通过 Parent 属性来定义这种关系,最终可以形成树形结构。
用户可以自定义类加载器,用于加载特定目录下的class文件
当 JVM 运行过程中,用户自定义了类加载器去加载某些类时,会按照下面的步骤(父类委托机制)
-
用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器 树的顶层
-
最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类
-
如果一直到底层的类加载都没有加载到,那么就会抛出异常 ClassNotFoundException
因此,按照这个过程可以想到,如果同样在 classpath 指定的目录中和自己工作目录中存放相同的 class,会优先加载 classpath 目录中的文件
双亲委派机制
什么是双亲委派机制
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操
作,如果上级的类加载器没有加载,自己才会去加载这个类。
双亲委派机制的作用
防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据
安全。
保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使 加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个.class对象。这样保证了class执行安全(如果子类加载器先加载,那么我们可以写一些与java.lang包中基础类同名的类, 然后再定义一个子类加载器,这样整个应用使用的基础类就都变成我们自己定义的类了。 )
Object类 -----> 自定义类加载器(会出现问题的,那么真正的Object类就可能被篡改了)
Tomcat的类加载机制
Tomcat 的类加载机制相对于 Jvm 的类加载机制做了一些改变。 没有严格的遵从双亲委派机制,也可以说打破了双亲委派机制
比如:有一个tomcat,webapps下部署了两个应用
app1/lib/a-1.0.jar com.lagou.edu.Abc
app2/lib/a-2.0.jar com.lagou.edu.Abc
不同版本中Abc类的内容是不同的,代码是不一样的
- 引导类加载器 和 扩展类加载器 的作用不变
- 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使用该变 量,而是加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。 位于CATALINA_HOME/bin下
- Common 通用类加载器加载Tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下, 比如servlet-api.jar
- Catalina ClassLoader 用于加载服务器内部可⻅类,这些类应用程序不能访问 Shared ClassLoader 用于加载应用程序共享类,这些类服务器不会依赖
- Webapp ClassLoader,每个应用程序都会有一个独一无二的Webapp ClassLoader,他用来加载 本应用程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
tomcat 8.5 默认改变了严格的双亲委派机制
首先从 Bootstrap Classloader 加载指定的类 如果未加载到,则从 /WEB-INF/classes 加载 如果未加载到,则从 /WEB-INF/lib/*.jar 加载
如果未加载到,则依次从 System、Common、Shared 加载(在这最后一步,遵从双亲委派机制)
Tomcat对https的支持
https简介
Http超文本传输协议,明文传输 ,传输不安全,https在传输数据的时候会对数据进行加密 ssl协议
TLS(transport layer security)协议
https和http的区别
HTTPS协议使用时需要到电子商务认证授权机构(CA)申请SSL证书
HTTP默认使用8080端口,HTTPS默认使用8443端口
HTTPS则是具有SSL加密的安全性传输协议,对数据的传输进行加密,效果上相当于HTTP的升级版
HTTP的连接是无状态的,不安全的;
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全
https的工作原理
Tomcat对https的支持
1、使用 JDK 中的 keytool 工具生成免费的秘钥库文件(证书)
keytool -genkey -alias terwer -keyalg RSA -keystore terwer.keystore
2、配置 conf/server.xml
maxThreads="150" schema="https" secure="true" SSLEnabled="true">
<SSLHostConfig>
<Certificate
certificateKeystoreFile="/Users/terwer/Documents/code/tomcat8/apache-tomcat-8.5.73/conf/terwer.keystore" certificateKeystorePassword="123456" type="RSA"
/>
</SSLHostConfig>
</Connector>
3、使用https协议访问8443端口(https://localhost:8443)。
Tomcat的性能优化策略
系统性能的衡量指标,主要是响应时间和吞吐量。
-
响应时间:执行某个操作的耗时;
-
吞吐量:系统在给定时间内能够支持的事务数量,单位为TPS(Transactions PerSecond的缩写,也 就是事务数/秒,一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。
Tomcat优化从两个方面进行
1)JVM虚拟机优化(优化内存模型)
2)Tomcat自身配置的优化(比如是否使用了共享线程池?IO模型?)
具体的效果需要根据生产环境调整到合适的参数
虚拟机运行优化
Java 虚拟机的运行优化主要是内存分配和垃圾回收策略的优化:
-
内存直接影响服务的运行效率和吞吐量
-
垃圾回收机制会不同程度地导致程序运行中断(垃圾回收策略不同,垃圾回收次数和回收效率都是
-
不同的)
Java虚拟机相关参数
JVM内存模型
参数调整实例
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -
XX:MaxMetaspaceSize=512m"
利用内存工具查看
ps -ef|grep tomcat
jhsdb jmap --heap --pid 21336
垃圾回收策略(GC策略)
垃圾回收性能指标
吞吐量:工作时间(排除GC时间)占总时间的百分比, 工作时间并不仅是程序运行的时间,还包 含内存分配时间。
暂停时间:由垃圾回收导致的应用程序停止响应次数/时间。
垃圾收集器
1、串行收集器(Serial Collector)
单线程执行所有的垃圾回收工作, 适用于单核CPU服务器 工作进程-----|(单线程)垃圾回收线程进行垃圾收集|---工作进程继续
2、并行收集器(Parallel Collector)
工作进程-----|(多线程)垃圾回收线程进行垃圾收集|---工作进程继续
又称为吞吐量收集器(关注吞吐量), 以并行的方式执行年轻代的垃圾回收, 该方式可以显著降 低垃圾回收的开销(指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态)。适用于多 处理器或多线程硬件上运行的数据量较大的应用
3、并发收集器(Concurrent Collector)
以并发的方式执行大部分垃圾回收工作,以缩短垃圾回收的暂停时间。适用于那些响应时间优先于 吞吐量的应用, 因为该收集器虽然最小化了暂停时间(指用户线程与垃圾收集线程同时执行,但不一 定是并行的,可能会交替进行), 但是会降低应用程序的性能
4、CMS收集器(Concurrent Mark Sweep Collector)
并发标记清除收集器, 适用于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处
理器资源的应用
5、G1收集器(Garbage-First Garbage Collector)
适用于大容量内存的多核服务器, 可以在满足垃圾回收暂停时间目标的同时, 以最大可能性实现 高吞吐量( JDK1.7之后)
调整垃圾收集器
JAVA_OPTS="-XX:+UseConcMarkSweepGC"
命令行打开 jconsole ,可以看到 jdk11 默认是 G1收集器
调整之后
Tomcat配置调优
Tomcat自身相关配置
1、调整线程池
2、调整连接器
3、禁用 AJP 连接器
4、调整I/O模式
可尝试使用 Apr IO模型,这个Apache底层库,依赖操作系统底层实现,性能较高
5、动静分离
可以使用Nginx+Tomcat相结合的部署方案,Nginx负责静态资源访问,Tomcat负责Jsp等动态资 源访问处理(因为Tomcat不擅⻓处理静态资源)。