Tomcat的类加载机制是指Tomcat在运行时如何加载和管理Java类。Tomcat的类加载机制的实现并没有严格遵守双亲委派原则,而是采用了一种层次化的类加载器结构,这种结构旨在提供更好的隔离性和灵活性,以支持多个Web应用程序的部署和运行。
但是,默认情况下,Server类加载器和Shared类加载器是未定义的,需要通过在conf/catalina.properties中定义server.loader和/或shared.loader属性的值,才会是这个更复杂的层次结构。
Server类加载器只对Tomcat内部可见,对于Web应用程序完全不可见。
Shared类加载器对所有Web应用程序可见,可以用于在所有Web应用程序之间共享代码。但是,对这些共享代码进行更新将需要重新启动Tomcat。
所以,真正的需要一定有的,并且我们通常需要关注的就是下面这个层级关系:
负责加载JVM自身的核心类库(如java.lang、java.util等)和JVM相关的类。启动类加载器是JVM的一部分,负责加载JVM运行所需的基础类。主要加载JRE中的lib包及lib/ext包下的内容。
负载加载Tomcat内部的一些核心类库,这些类一般在$CATALINA_HOME/bin这个目录下, 一般包含bootstarap.jar、tomcat-juli.jar以及common-daemon.jar等。
负责加载Tomcat的公共类和库,位于$CATALINA_HOME/lib目录下的JAR文件。这些类库是Tomcat启动时加载的,是整个Tomcat实例中共享的类。
每个Web应用程序都有一个独立的Web应用程序类加载器,负责加载该Web应用程序的类和资源。它从$CATALINA_HOME/webapps/<webapp_name>/WEB-INF/classes目录和$CATALINA_HOME/webapps/<webapp_name>/WEB-INF/lib目录加载类和JAR文件。
以上几个加载器中Bootstrap、System、Common和Webapp之间,还是会遵守双亲委派的,也就是说还是先委派给上一层类加载器加载,加载不了再有自己加载。
但是多个Webapp ClassLoader之间是没有委派关系的,他们就是各自加载各自需要加载的Jar包。
由于每个Web应用程序都有自己的类加载器,因此不同Web应用程序中的类可以使用相同的类名,而不会产生命名冲突。
同时,由于每个Web应用程序都有自己的类加载器,因此在卸载一个Web应用程序时,它的所有类都会从内存中清除,这可以避免内存泄漏的问题。
这种层次化的类加载器结构和委派机制确保了类的唯一性和隔离性,避免了类的重复加载和冲突,同时也实现了多个Web应用程序的隔离和独立运行。
在实际应用中,设置线程池的核心线程数需要综合考虑多个因素,包括系统的硬件资源、应用的业务场景、应用线程的执行时间等等。因此,线程池核心线程数的推荐值仅仅是一个基础的参考值。
对于Tomcat而言,默认最大线程数是200,默认最小空闲线程数是10。
我认为,这些值应该是经过Tomcat开发团队反复测试和验证的结果,是适合绝大多数场景的。
我们作为使用者,大致可以猜测一下,Tomcat可以设置为200,可能有以下原因:
-
Tomcat作为Web服务器,其主要的场景是处理短连接请求。相较于其他应用服务器,Tomcat的请求处理时间相对较短,因此每条请求都不会占用时间太长。
-
Tomcat作为Web服务器,与一般的应用程序不同,它需要处理大量的并发请求。因此,默认线程数设置的大一些,可以满足大多数Web应用的需求。
-
在Tomcat的默认线程池中,使用了多个优化策略,如可回收线程、无锁化算法等,这些策略可以有效地减少线程创建和销毁的开销,提高线程池的吞吐量和性能。
Tomcat的设计目标是高吞吐量和低延迟,不遵守线程池核心线程数推荐公式,是因为其默认线程池的设置已经经过充分的优化和测试,能够满足大多数的应用场景。
其实在实际工作中,我们也建议大家不要完全按照公式配置,而是根据你的实际业务情况进行充分压测之后,进行合理的配置。
Tomcat是一个基于Servlet规范实现的Java Web容器,所以,在接收并处理请求的过程中,Servlet是必不可少的。
主要大致流程可以分为以下几步:
-
接收请求
-
请求解析
-
Servlet查找
-
Servlet请求处理
-
请求返回
接收请求:Tomcat通过连接器监听指定的端口和协议,接收来自客户端的HTTP请求。
请求解析:接收到请求之后,Tomcat首先会解析请求信息,包括请求方法、URL、请求头参数等。
Servlet查找:根据解析出来的URL,找到对应的Servlet,并把请求交给他进行处理。
Servlet处理:这个过程就把请求交给Servlet进行处理,主要是执行其中的service方法进行请求处理。
请求返回:在Servlet处理结束后,把请求的响应在发送给客户端。
Servlet在处理请求的过程中,要经历一个完整的生命周期,主要包含了以下三个阶段,分别执行三个方法,init、service和destory。每一次请求至少要经过service方法的执行,而init和destory并不需要每一个请求都执行。
初始化阶段:当Servlet容器加载Servlet时,会创建一个Servlet实例,并调用其init()方法进行初始化。在init()方法中,可以执行一些初始化操作,例如读取配置文件、连接数据库等。
处理请求阶段:在Servlet初始化完成后,当有客户端请求到达时,Servlet容器会创建一个请求对象(HttpServletRequest)和响应对象(HttpServletResponse),并调用Servlet的service()方法来处理请求。在service()方法中,Servlet可以通过请求对象获取客户端请求的信息,然后根据请求内容生成响应结果。
销毁阶段:当Servlet容器关闭或Web应用程序卸载时,会调用Servlet的destroy()方法进行销毁。在destroy()方法中,可以执行一些清理操作,例如关闭连接、释放资源等。
一般这个问题,主要是因为在SpringMVC的应用中,过滤器和拦截器都是用来对请求进行预处理、过滤、拦截的,所以经常会放在一起比较。不过他们其实还有一些区别的。
他们的主要区别在于作用和生效的位置不同,过滤器是在请求进入Servlet容器之前拦截请求并对请求进行处理,而拦截器是在请求进入Servlet容器之后,但在进入Controller之前拦截请求并对请求进行处理,也可以在响应返回客户端之前,拦截响应并对响应进行处理。
在Tomcat中,一次请求会先进入到Tomcat容器,然后经过Filter的处理,处理通过之后才会进入到Servlet容器,进入到Servlet容器之后,才会在Servlet执行的前后执行Intercepter。
过滤器在请求进入Servlet容器之前拦截请求并对请求进行处理,比如对请求进行安全验证、日志记录等,之后将请求转发给对应的Servlet进行处理。过滤器是基于Java Servlet规范实现的,可以通过配置web.xml文件进行实现。
拦截器是在请求进入Servlet容器之后,拦截请求并对请求进行处理,也可以在响应返回客户端之前,拦截响应并对响应进行处理。拦截器可以对请求进行更加精细的控制,例如进行AOP、权限控制、事务管理等操作。拦截器是基于Spring框架实现的,可以通过定义拦截器类实现。
定义一个过滤器,需要实现javax.servlet.Filter接口:
public class LoginFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
// 初始化
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 登录验证和处理
chain.doFilter(req, resp);
}
public void destroy() {
// 销毁
}
}并且需要在web.xml文件中把他配置上:
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>com.hollis.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>或者通过注解也可以。
在 LoginFilter上添加@WebFilter注解,并在启动类上增加@ServletComponentScan("com.hollis.filter.LoginFilter")注解。也可替代xml文件中的配置。
想要定义一个拦截器,需要实现org.springframework.web.servlet.HandlerInterceptor接口:
@Component
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 登录验证和处理
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后处理
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 完成处理
}
}并且在SpringMVC中进行配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/api/*"/>
<bean class="com.hollis.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>或者
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api/*");
}
}关于Tomcat的IO模型,不同的版本中是不太一样的,下面是一张Tomcat官网中各个历史重要版本所采用IO模型的介绍:
综合来说就是Tomcat支持多种IO模型,包括了标准的BIO(Blocking I/O)、NIO(Non-Blocking I/O)、NIO2(即JDK 1.7中的AIO)和APR(Apache Portable Runtime)。
-
BIO是最传统的线程模型,也称为阻塞I/O。在BIO模型中,每个客户端连接都由一个独立的线程处理。当有新的连接到来时,Tomcat会创建一个新的线程来处理请求。这意味着每个连接都需要一个独立的线程,当并发连接数较大时,会导致线程数急剧增加,占用大量系统资源,并且可能出现线程切换带来的开销。
-
NIO是Java的新I/O库(java.nio)的线程模型。在NIO模型中,通过使用Java NIO的选择器(Selector)机制,一个线程可以同时处理多个连接的请求。NIO模型相对于BIO模型来说,能够支持更多的并发连接,并且在连接数较大时对系统资源的消耗较少,但由于在应用层需要处理I/O事件,编程较为复杂。
-
NIO2是Java 7引入的进一步改进的NIO模型,也叫AIO。在NIO2中,Java提供了更多的异步I/O操作,包括异步文件I/O、异步套接字I/O等。这使得Tomcat能够更好地支持异步请求处理,提高了处理性能和效率。
-
APR是Apache软件基金会提供的一个库,它为应用程序提供了跨平台的抽象层,提供了高性能的本地I/O支持。在APR模型中,Tomcat利用本地操作系统的特性进行I/O操作,包括网络和文件I/O。APR模型在性能方面表现得非常出色,特别是在处理大量并发连接时,因为它直接利用底层操作系统的异步I/O能力。
我们可以在Tomcat启动的时候,可以通过log看到Connector使用的是哪一种运行模式:
Starting ProtocolHandler [“http-bio-8080”]
Starting ProtocolHandler [“http-nio-8080”]
Starting ProtocolHandler [“http-apr-8080”]
切换Tomcat使用的I/O模型通常涉及配置Tomcat的连接器(Connector)。不同的I/O模型对应着不同的连接器实现。
在server.xml文件中,通过配置Connecter来选择不同的IO模型,如:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />这里的protocol就是可以修改来替换成其他的协议,即IO模型的地方。
各个IO模型对应的关系如下:
-
BIO模型:将protocol属性设置为HTTP/1.1(默认值),或者可以显式地设置为org.apache.coyote.http11.Http11Protocol。
-
NIO模型:将protocol属性设置为org.apache.coyote.http11.Http11NioProtocol。
-
NIO2模型:将protocol属性设置为org.apache.coyote.http11.Http11Nio2Protocol。
-
APR模型:将protocol属性设置为org.apache.coyote.http11.Http11AprProtocol。
但是记得修改后需要重启tomcat才会生效。而且需要注意,Tomcat的不同版本可能支持不同的I/O模型,因此请根据您使用的Tomcat版本进行相应的配置。
一般来说,建议使用NIO或者NIO2就行了,对于涉及大量I/O操作的应用,例如Web服务、聊天应用或在线游戏等,NIO或NIO2模型都挺合适。
如果对性能要求非常高,可以考虑使用APR模型,但需要安装和配置APR库,有一定的成本。


