Tomcat

  Tomacat是由Apache推出的一款免费开源的Servlet容器,可实现JavaWeb程序的装载。Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。

一、Tomcat架构

1.1 Tomcat工作模式

  • 1、独立的servlet容器
      Tomcat作为独立的Web服务器来单独运行,Servlet容器组件作为Web服务器中的一部分而存在。这是Tomcat的默认工作模式。
      在这种模式下,Tomcat是一个独立运行的Java程序。和运行其他Java程序一样,运行Tomcat需要启动一个Java虚拟机(JVM)进程,由该进程来运行Tomcat。
  • 2、其他Web服务器进程内的Servlet容器
      在这种模式下,Tomcat分为Web服务器插件和Servlet容器组件两部分。Web服务器插件在其他Web服务器进程的内部地址空间启动一个Java虚拟机,Servlet容器组件在此Java虚拟机中运行。
      如有客户端发出调用Servlet的请求,Web服务器插件获得对此请求的控制并将它转发(使用JNI通信机制)给Servlet容器组件。

      JNI指的是Java本地调用接口,通过这一接口,Java程序可以和采用其他语言编写的本地程序进行通信。
      进程内的Servlet容器对于单进程、多线程的Web服务器非常合适,可以提供较高的运行速度,但缺乏伸缩性。
  • 3、其他Web服务器进程外的Servlet容器
      在这种模式下,Tomcat分为Web服务器插件和Servlet容器组件两部分。Web服务器插件在其他Web服务器的外部地址空间启动一个Java虚拟机进程,Servlet容器组件在此Java虚拟机中运行。
      如有客户端发出调用Servlet的请求,Web服务器插件获得对此请求的控制并将它转发(采用IPC通信机制)给Servlet容器。IPC(Inter-Process Communication,进程间通信)是两个进程之间进行通信的一种机制。

      进程外Servlet容器对客户请求的响应速度不如进程内Servlet容器,但进程外容器具有更好的伸缩性和稳定性。

  可以看出,当Tomcat作为独立的Servlet容器来运行时,此时Tomcat是能运行Java Servlet的独立Web服务器。此外,Tomcat还可作为其他Web服务器进程内或者进程外的Servlet容器,从而与其他Web服务器集成(如Apache和IIS服务器等)。集成的意义在于:对于不支持运行Java Servlet的其他Web服务器,可通过集成Tomcat来提供运行Servlet的功能。

1.2 Tomcat顶层结构*

  Tomcat的顶层结构图:

  Tomcat中最顶层的容器是Server,代表着整个服务器。从上图中可以看出,一个Server可以包含至少一个Service,即可以包含多个Service,用于具体提供服务
  Service主要包含两个部分:Connector和Container。Tomcat的心脏就是这两个组件,他们的作用:

  1. Connector用于处理连接相关的事情,并提供Socket与Request请求和Response响应相关的转换;
  2. Container用于封装和管理Servlet,以及具体处理Request请求。

  一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接。示意图:

  多个Connector和一个Container就形成了一个Service,有了Service就可以对外提供服务。但是Service还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就是Server。所以整个Tomcat的生命周期由Server控制。
  上述的包含关系或者说是父子关系,都可以在Tomcat的conf目录下的server.xml配置文件中看出,下图是删除了注释内容之后的一个完整的server.xml配置文件(Tomcat版本为8.0):

  上边的配置文件,还可以通过下边的一张结构图来帮助理解:

  Server标签设置的端口号为8005,shutdown=”SHUTDOWN” ,表示在8005端口监听“SHUTDOWN”命令,如果接收到了就会关闭Tomcat。一个Server有一个Service,当然还可以进行配置,一个Service有多个Connector,Service左边的内容都属于Container的,Service下边是Connector。

  • Tomcat架构小结
     1、Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container;
     2、Server掌管着整个Tomcat的生死大权;
     3、Service是对外提供服务的;
     4、Connector用于接受请求并将请求封装成Request和Response来具体处理;
     5、Container用于封装和管理Servlet,以及具体处理request请求。

1.3 Connector和Container的关系

  由上述内容我们大致可以知道一个请求发送到Tomcat之后,首先经过Service然后交给Connector,Connector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后再由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了。
  Connector最底层使用的是Socket来进行连接的,Request和Response是按照HTTP协议来封装的,所以Connector同时需要实现TCP/IP协议和HTTP协议。
  Tomcat既然需要处理请求,那么肯定需要先接收到这个请求,因此先看一下Connector。

1.4 Connector架构

  Connector用于接受请求并将请求封装成Request,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。 因此,我们可以把Connector分为四个方面进行理解:

  1. Connector如何接受请求的?
  2. 如何将请求封装成Request和Response的?
  3. 封装完之后的Request和Response如何交给Container进行处理的?
  4. Container处理完之后如何交给Connector并返回给客户端的?

  Connector的结构图:

  Connector就是使用ProtocolHandler来处理请求的,不同的ProtocolHandler 代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的, Http11NioProtocol使用的是NioSocket来连接的。
  ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter。
  Endpoint用来处理底层Socket的网络连接;Processor用于将Endpoint接收到的Socket封装成Request;Adapter用于将Request交给Container进行具体的处理。
  Endpoint由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的,而Processor用来实现HTTP协议的,Adapter将请求适配到Servlet容器进行具体的处理。
  Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和 AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步Request的超时,Handler用于处理接收 到的Socket,在内部调用Processor进行处理。

1.5 Container结构

  Container用于封装和管理Servlet,以及具体处理Request请求,在Container内部包含了4个子容器,结构图:

  4个子容器的作用分别是:
   1、Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
   2、Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
   3、Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件;
   4、Wrapper:每一Wrapper封装着一个Servlet;
  下面找一个Tomcat的文件目录对照一下,如下图:

  Context和Host的区别是Context表示一个应用,Tomcat中默认的配置下webapps下的每一个文件夹目录都是一个Context,其中ROOT目录中存放着主应用,其他目录存放着子应用,而整个webapps就是一个Host站点。
  当访问应用Context的时候,如果是ROOT下的则直接使用域名就可以访问,例如:www.baidu.com,如果是Host(webapps)下的其他应用,则可以使用www.baidu.com/docs进行访问,当然默认指定的根应用(ROOT)是可以进行设定的,只不过Host站点下默认的主应用是ROOT目录下的。
  看到这里我们知道Container是什么,但是还是不知道Container是如何进行请求处理的以及处理完之后是如何将处理完的结果返回给Connector的。

1.5 Container如何处理请求

  Container处理请求是使用Pipeline-Valve管道来处理的。Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的结果返回,再让下一个处理者继续处理。
  Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同,区别主要有以下两点:
   1、每个Pipeline都有特定的Valve,而且是在管道的最后一个执行,这个Valve叫做BaseValve,BaseValve是不可删除的;
   2、在上层容器的管道的BaseValve中会调用下层容器的管道。
  Container包含四个子容器,而这四个子容器对应的BaseValve分别在:StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。
  Pipeline的处理流程图:

  • Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline(Engine的管道);
  • 在Engine的管道中依次会执行EngineValve1、EngineValve2等等,最后会执行StandardEngineValve,在StandardEngineValve中会调用Host管道,然后再依次执行Host的HostValve1、HostValve2等,最后在执行StandardHostValve,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValve。
  • 当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理。
  • 当所有的Pipeline-Valve都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端。

二、Tomcat的生命周期

  在Tomcat启动时,会读取server.xml文件创建Server、Service、Connector、Engine、Host、Context、Wrapper等组件。

  • 1、Lifestyle
      Tomcat 中的所有组件都继承了Lifecycle接口,Lifecycle接口定义了一整套生命周期管理的函数,从组件的新建、初始化完成、启动、停止、失败到销毁,都遵守同样的规则,Lifecycle组件的状态转换图:

      正常的调用顺序是init()->start()->destroy(),父组件的init()和start()会触发子组件的init()和start(),所以Tomcat中只需调用Server组件的init()和start()即可。
      每个实现组件都继承自LifecycleBase,LifecycleBase实现了Lifecycle接口,当容器状态发生变化时,都会调用fireLifecycleEvent方法,生成LifecycleEvent,并且交由此容器的事件监听器处理。

2.1 Tomcat的启动流程


  tomcat/bin/startup.sh脚本,是启动org.apache.catalina.startup.Bootstra类的main方法,并传入start参数。
  Tomcat启动的主要步骤:

  1. 新建Bootstrap对象daemon,并调用其init()方法;
  2. 初始化Tomcat的类加载器(init);
  3. 用反射实例化org.apache.catalina.startup.Catalina对象catalinaDaemon(init);
  4. 调用daemon的load方法,实质上调用了catalinaDaemon的load方法(load);
  5. 加载和解析server.xml配置文件(load);
  6. 调用daemon的start方法,实质上调用了catalinaDaemon的start方法 (start);
  7. 启动Server组件,Server的启动会带动其他组件的启动,如Service、Container、Connector(start);
  8. 调用catalinaDaemon的await方法,循环等待接收Tomcat的shutdown命令。
2.1.1 BootStrap的init阶段

  init方法的主要内容:

  1. 初始化类加载器catalinaLoader并将其设为当前线程的父加载器。
  2. 预加载tomcat、javax包等自定义类。
  3. 一个org.apache.catalina.startup.Catalina对象(catalinaDaemon),该对象则负责了后续整个tomcat服务器的启动工作。

  源码:

public void init() throws Exception {
	//初始化commonLoader、catalinaLoader和sharedLoader;
	initClassLoaders();
	//将catalinaLoader设置为Tomcat主线程的线程上下文类加载器;
	Thread.currentThread().setContextClassLoader(catalinaLoader);
	SecurityClassLoad.securityClassLoad(catalinaLoader);
	if (log.isDebugEnabled())
		log.debug("Loading startup class");
	Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
	Object startupInstance = startupClass.getConstructor().newInstance();
	if (log.isDebugEnabled())
		log.debug("Setting startup class properties");
	String methodName = "setParentClassLoader";
	Class<?> paramTypes[] = new Class[1];
	paramTypes[0] = Class.forName("java.lang.ClassLoader");
	Object paramValues[] = new Object[1];
	paramValues[0] = sharedLoader;
	Method method =
		startupInstance.getClass().getMethod(methodName, paramTypes);
	method.invoke(startupInstance, paramValues);
	catalinaDaemon = startupInstance;
}
2.1.2 BootStrap的load阶段

  load方法的主要内容:

  1. 创建server.xml解析器digester实例。
  2. digester实例解析server.xml文件内容、并创建standardServer实例。
  3. standardServer实例绑定catalina容器、设置catalina所在路径。
  4. 初始化standardServer实例及所有组件Service、Connector、Engine等。

  源码:

private void load(String[] arguments) throws Exception {
	String methodName = "load";
	Object param[];
	Class<?> paramTypes[];
	if (arguments==null || arguments.length==0) {
		paramTypes = null;
		param = null;
	} else {
		paramTypes = new Class[1];
		paramTypes[0] = arguments.getClass();
		param = new Object[1];
		param[0] = arguments;
	}
	Method method =
		catalinaDaemon.getClass().getMethod(methodName, paramTypes);
	if (log.isDebugEnabled())
		log.debug("Calling startup class " + method);
	method.invoke(catalinaDaemon, param);
}
2.1.3 BootStrap的start阶段

  start方法的主要内容:

  1. 判断catalinaDaemon后台实例是否完成初始化,若没有则重新实例化。
  2. 反射调用catalinaDaemon的start()方法,依序地启动server、service、engine容器、connector连接器等组件,若启动过程中出现异常则调用destory()方法销毁所有组件。
  3. catalinaDaemon注册钩子函数,保证standardServer、logManager实例在关闭时能被关闭、销毁。

  源码:

public void start() throws Exception {
	if( catalinaDaemon==null ) init();
	Method method = catalinaDaemon.getClass().getMethod("start", (Class[] )null);
	method.invoke(catalinaDaemon, (Object [])null);
}

2.2 Tomcat的停止流程

  catalinaDaemon调用await等待停止命令,开发者一般是通过执行tomcat/bin/shutdown.sh来关闭Tomcat,等价于执行org.apache.catalina.startup.Bootstra类的main方法,并传入stop参数。
  停止逻辑:

  1. 新建Bootstrap对象 daemon,并调用其 init()方法;
  2. 初始化Tomcat的类加载器;
  3. 用反射实例化org.apache.catalina.startupCatalina对象catalinaDaemon;
  4. 调用daemon的stopServer方法,实质上调用了catalinaDaemon的stopServer方法;
  5. 解析server.xml文件,构造出Server容器;
  6. 获取Server的socket监听端口和地址,创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令;
  7. 运行中的Server调用stop方法停止。

  BootStrap的stopServer方法源码:

public void stopServer(String[] arguments) throws Exception {
	Object param[];
	Class<?> paramTypes[];
	if (arguments==null || arguments.length==0) {
		paramTypes = null;
		param = null;
	} else {
		paramTypes = new Class[1];
		paramTypes[0] = arguments.getClass();
		param = new Object[1];
		param[0] = arguments;
	}
	Method method =
		catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
	method.invoke(catalinaDaemon, param);
}

2.3 请求处理

2.3.1 Connector

  在Tomcat9中,Connector支持的协议是HTTP和AJP,协议处理类分别对应org.apache.coyote.http11.Http11NioProtocol和org.apache.coyote.http11.Http11AprProtocol(已经取消BIO模式)。
  Connector主要包含三个模块:Http11NioProtocol、Mapper、CoyoteAdapter。http请求在Connector中的流程:

  1. Acceptor为监听线程,调用serverSocketAccept()阻塞,本质上调用ServerSocketChannel.accept();
  2. Acceptor将接收到的Socket添加到Poller池中的一个Poller;
  3. Poller通过worker线程把socket包装成SocketProcessor;
  4. SocketProcessor调用getHandler()获取对应的ConnectionHandler;
  5. ConnectionHandler把socket交由Http11Processor处理,解析http的Header和Body;
  6. Http11Processor调用service()把包装好的request和response传给CoyoteAdapter;
  7. CoyoteAdapter会通过Mapper,把请求对应的session、servlet等关联好,准备传给
    Container。
2.3.2 Container

  有4个Container,采用了责任链的设计模式。
  Pipeline就像是每个容器的逻辑总线,在Pipeline上按照配置的顺序,加载各个Valve。通过Pipeline完成各个Valve之间的调用,各个Valve实现具体的应用逻辑。每个请求在Pipeline上流动,经过每个Container(对应着一个或多个Valve阀门),各个Container按顺序处理请求,最终在Wrapper结束。

  Connector中的CoyoteAdapter会调用invoke(),把request和response传给Container,Container中依次调用各个Valve,每个Valve的作用:

  1. StandardEngineValve:StandardEngine中的唯一阀门,主要用于从request中选择其host映射的Host容器StandardHost;
  2. AccessLogValve:StandardHost中的第一个阀门,主要用于管道执行结束之后记录日志信息;
  3. ErrorReportValve:StandardHost中紧跟AccessLogValve的阀门,主要用于管道执行结束后,从request对象中获取异常信息,并封装到response中以便将问题展现给访问者;
  4. StandardHostValve: StandardHost中最后的阀门,主要用于从request中选择其context 映射的Context容器StandardContext以及访问request中的Session以更新会话的最后访问时间;
  5. StandardContextValve:StandardContext中的唯一阀门,主要作用是禁止任何对WEB-INF或META-INF目录下资源的重定向访问,对应用程序热部署功能的实现,从request中获得StandardWrapper;
  6. StandardWrapperValve : StandardWrapper中的唯一阀门,主要作用包括调用StandardWrapper的loadServlet方法生成Servlet实例和调用ApplicationFilterFactory 生成Filter链。

  最终将Response返回给Connector完成一次http的请求。

2.3.3 NioEndPoint

  在Tomcat中,Endpoint主要用来接收网络请求,处理则由ConnectionHandler来执行。

  NioEndPoint包含了三个组件Acceptor、Poller和SocketProcessor:
  Acceptor:后台线程,负责监听请求,将接收到的Socket请求放到Poller队列中。
  Poller:后台线程,当Socket就绪时,将Poller队列中的Socket交给Worker线程池处理。
  SocketProcessor(Worker):处理socket,本质上委托ConnectionHandler处理。
  Connector启动以后会启动一组线程用于不同阶段的请求处理过程。Acceptor、Poller、worker所在的ThreadPoolExecutor都维护在NioEndpoint中。
  Acceptor线程组。用于接受新连接,并将新连接封装一下,选择一个Poller将新连接添加到Poller的事件队列中。
  Poller线程组。用于监听Socket事件,当Socket可读或可写等等时,将Socket封装一下添加到worker线程池的任务队列中。
  worker线程组。用于对请求进行处理,包括分析请求报文并创建Request对象,调用容器的pipeline进行处理。

  • 1、 Acceptor的run方法
  1. Acceptor在启动后会阻塞在ServerSocketChannel.accept();方法处,当有新连接到达时,该方法返回一个SocketChannel。
  2. 配置完Socket以后,将Socket封装到NioChannel中,并注册到Poller。注意:一开始就启动了多个Poller线程,注册的时候,连接是公平的分配到每个Poller的。NioEndpoint维护了一个Poller数组,当一个连接分配给pollers[index]时,下一个连接就会分配给 pollers[(index+1)%pollers.length]
  3. addEvent()方法会将Socket添加到该Poller的PollerEvent队列中。到此Acceptor的任务就完成了。

  Acceptor的run方法源码:

public void run() {
	int errorDelay = 0;
	// Loop until we receive a shutdown command
	while (endpoint.isRunning()) {
		// Loop if endpoint is paused
		while (endpoint.isPaused() && endpoint.isRunning()) {
			state = AcceptorState.PAUSED;
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				// Ignore
			}
		}
		if (!endpoint.isRunning()) {
			break;
		}
		state = AcceptorState.RUNNING;
		try {
			//if we have reached max connections, wait
			endpoint.countUpOrAwaitConnection();
			// Endpoint might have been paused while waiting for latch
			// If that is the case, don't accept new connections
			if (endpoint.isPaused()) {
				continue;
			}
			U socket = null;
			try {
				// Accept the next incoming connection from the server
				// socket
				socket = endpoint.serverSocketAccept();
			} catch (Exception ioe) {
				// We didn't get a socket
				endpoint.countDownConnection();
				if (endpoint.isRunning()) {
					// Introduce delay if necessary
					errorDelay = handleExceptionWithDelay(errorDelay);
					// re-throw
					throw ioe;
				} else {
					break;
				}
			}
			// Successful accept, reset the error delay
			errorDelay = 0;
			// Configure the socket
			if (endpoint.isRunning() && !endpoint.isPaused()) {
				// setSocketOptions() will hand the socket off to
				// an appropriate processor if successful
				if (!endpoint.setSocketOptions(socket)) {
					endpoint.closeSocket(socket);
				}
			} else {
				endpoint.destroySocket(socket);
			}
		} catch (Throwable t) {
			ExceptionUtils.handleThrowable(t);
			String msg = sm.getString("endpoint.accept.fail");
			// APR specific.
			// Could push this down but not sure it is worth the trouble.
			if (t instanceof Error) {
				Error e = (Error) t;
				if (e.getError() == 233) {
					// Not an error on HP-UX so log as a warning
					// so it can be filtered out on that platform
					// See bug 50273
					log.warn(msg, t);
				} else {
					log.error(msg, t);
				}
			} else {
				log.error(msg, t);
			}
		}
	}
	state = AcceptorState.ENDED;
}
  • 2、Poller的run方法
  1. 当Poller启动后,因为Selector中并没有已注册的Channel,所以当执行到该方法时只能阻塞。所有的Poller共用一个Selector,其实现类是sun.nio.ch.EPollSelectorImpl。
  2. events()方法会将通过addEvent()方法,添加到事件队列中的Socket注册到EPollSelectorImpl,当Socket可读时,Poller才对其进行处理。
  3. createSocketProcessor()方法,将Socket封装到SocketProcessor中,SocketProcessor实现了Runnable接口。worker线程通过调用其run()方法来对Socket进行处理。
  4. execute(SocketProcessor)方法,将SocketProcessor提交到线程池,放入线程池的workQueue中。workQueue是BlockingQueue的实例。到此Poller的任务就完成了。

  Poller的run方法源码:

public void run() {
	// Loop until destroy() is called
	while (true) {
		boolean hasEvents = false;
		try {
			if (!close) {
				hasEvents = events();
				if (wakeupCounter.getAndSet(-1) > 0) {
					//if we are here, means we have other stuff to do
					//do a non blocking select
					keyCount = selector.selectNow();
				} else {
					keyCount = selector.select(selectorTimeout);
				}
				wakeupCounter.set(0);
			}
			if (close) {
				events();
				timeout(0, false);
				try {
					selector.close();
				} catch (IOException ioe) {
					log.error(sm.getString("endpoint.nio.selectorCloseFail"),ioe);
				}
				break;
			}
		} catch (Throwable x) {
			ExceptionUtils.handleThrowable(x);
			log.error("",x);
			continue;
		}
		//either we timed out or we woke up, process events first
		if ( keyCount == 0 ) hasEvents = (hasEvents | events());
		Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
		// Walk through the collection of ready keys and dispatch
		// any active event.
		while (iterator != null && iterator.hasNext()) {
			SelectionKey sk = iterator.next();
			NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
			// Attachment may be null if another thread has called
			// cancelledKey()
			if (attachment == null) {
				iterator.remove();
			} else {
				// 有 Socket 出现读事件
				iterator.remove();
				processKey(sk, attachment);
			}
		}//while
		//process timeouts
		timeout(keyCount,hasEvents);
	}//while
	getStopLatch().countDown();
}
  • 3、Worker的run方法
  1. worker线程被创建以后就执行ThreadPoolExecutor的runWorker()方法,试图从workQueue中取待处理任务,但是一开始workQueue是空的,所以worker线程会阻塞在 workQueue.take()方法。
  2. 当新任务添加到workQueue后,workQueue.take()方法会返回一个Runnable,通常是SocketProcessor,然后worker线程调用SocketProcessor的run()方法对Socket进行处理。
  3. createProcessor()会创建一个Http11Processor,它用来解析Socket,将Socket中的内容封装到Request中。注意这个Request是临时使用的一个类,它的全类名是org.apache.coyote.Request,
  4. postParseRequest()方法封装一下Request,并处理一下映射关系(从URL映射到相应的Host、Context、Wrapper)。
  5. CoyoteAdapter将Rquest提交给Container处理之前,并将org.apache.coyote.Request 封装到org.apache.catalina.connector.Request,传递给Container处理的Request是org.apache.catalina.connector.Request。
  6. connector.getService().getMapper().map(),用来在Mapper中查询URL的映射关系。映射关系会保留到org.apache.catalina.connector.Request中,Container处理阶段,request.getHost()是使用的就是这个阶段查询到的映射主机,以此类推request.getContext()、request.getWrapper()都是。
  7. connector.getService().getContainer().getPipeline().getFirst().invoke(),会将请求传递到Container处理,当然了Container处理也是在Worker线程中执行的。

三、Tomcat类加载器*

  Tomcat不能直接使用系统的类加载器,必须要实现自定义的类加载器。
  Servlet应该只允许加载 WEB-INF/classes目录及其子目录下的类,和从部署的库到WEB-INF/lib目录加载类,实现不同的应用之间的隔离。另一个要实现自定义类加载器的原因是,为了提供热加载的功能。如果WEB-INF/classes或WEB-INF/lib目录下的类发生变化时,Tomcat应该会重新加载这些类。在Tomcat的类加载中,类加载使用一个额外的线程,不断检查Servlet类和其他类的文件的时间戳。Tomcat所有类加载器必须实现Loader接口,支持热加载的还需要实现Reloader接口。

  • Tomcat类加载器结构

      commonLoader、catalinaLoader和sharedLoader是在Tomcat容器初始化时创建的。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,并且使用catalinaLoader加载Tomcat容器自身的class。
      它们三个都是URLClassLoader类的一个实例,只是它们的类加载路径不一样,在tomcat/conf/catalina.properties 配置文件中配置(common.loader,server.loader,shared.loader)。

3.1 应用隔离

  对于每个webapp应用,都会对应唯一的StandContext,在StandContext中会引用WebappLoader,该类又会引用WebappClassLoader,WebappClassLoader就是真正加载webapp的classloader。
  不同的StandardContext有不同的WebappClassLoader,那么不同的webapp 的类加载器就是不一致的。加载器的不一致带来了名称空间不一致,所以webapp之间是相互隔离的。
  WebappClassLoader加载class的步骤:

  1. 先检查 webappclassloader 的缓存是否有该类。
  2. 为防止webapp覆盖java se类,尝试用application classloader(应用类加载器)加载。
  3. 尝试WebappClassLoader自己加载class。
  4. 最后无条件地委托给父加载器common classloader,加载CATALINA_HOME/lib下的类。
  5. 如果都没有加载成功,则抛出ClassNotFoundException异常。

3.2 类加载机制*

  双亲委派模型的类加载过程主要分为以下几个步骤:

1)初始化 ClassLoader 时需要指定自己的 parent 是谁。
2)先检查类是否已经被加载过,如果类已经被加载了,直接返回。
3)若没有加载则调用父加载器 parent 的 loadClass() 方法进行加载。
4)若父加载器为空则默认使用启动类加载器 bootstrap ClassLoader 进行加载。
5)如果父类加载失败,抛出 ClassNotFoundException 异常后,再调用自己的 findClass() 方法进行加载。

  如果想要破坏这种机制,那么就自定义一个类加载器(继承自 ClassLoader),并重写其中的 loadClass() 方法,使其不进行双亲委派即可。最经典例子就是 Tomcat 容器的类加载机制了,它实现了自己的类加载器 WebApp ClassLoader,并且打破了双亲委派模型,在每个应用在部署后,都会创建一个唯一的类加载器。

  • Tomcat 的类加载器结构图

      Common ClassLoader:加载 common.loader 属性下的 jar,一般是 CATALINA_HOME/lib 目录下,主要是 tomcat 使用以及应用通用的一些类。
      Catalina ClassLoader:加载 server.loader 属性下的 jar,默认未配置路径,返回其父加载器即 Common ClassLoader,主要是加载服务器内部可用类,这些类应用程序不能访问。
      Shared Classloader:加载 share.loader 属性下的jar,默认未配置路径,返回其父加载器即 Common ClassLoader,主要是加载应用程序共享类,这些类对 Tomcat 自己不可见。
      WebApp ClassLoader:Tomcat 可以存在多个 WebApp ClassLoader 实例,每个应用程序都会有1个独自使用的 WebApp ClassLoader,用来加载本应用程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
  • Tomcat 的类加载流程
      当 Tomcat 使用 WebAppClassLoader 进行类加载时,具体过程:
      1)先在本地 cache 缓存中查找该类是否已经加载过,看看 Tomcat 有没有加载过这个类。
      2)如果 Tomcat 没有加载过这个类,则从系统类加载器的 cache 缓存中查找是否加载过。
      3)如果没有,则使用 ExtClassLoader 类加载器类加载,重点来了,Tomcat 的 WebAppClassLoader 并没有先使用 AppClassLoader 来加载类,而是直接使用了 ExtClassLoader 来加载类。不过 ExtClassLoader 依然遵循双亲委派,它会使用 Bootstrap ClassLoader 来对类进行加载,保证了 Jre 里面的核心类不会被重复加载。
      比如在 Web 中加载一个 Object 类。WebAppClassLoader → ExtClassLoader → Bootstrap ClassLoader,这个加载链,就保证了 Object 不会被重复加载。
      4)如果没有加载成功,WebAppClassLoader 就会调用自己的 findClass() 方法由自己来对类进行加载,先在 WEB-INF/classes 中加载,再从 WEB-INF/lib 中加载。
      5)如果仍然未加载成功,WebAppclassLoader 会委派给 SharedClassLoader,SharedClassLoad 再委派给 CommonClassLoader,CommonClassLoader 委派给 AppClassLoader,直到最终委派给 BootstrapClassLoader,最后再一层一层地在自己目录下对类进行加载。
      6)都没有加载成功的话,抛出异常。
  • 源码
      WebappClassLoader 应用类加载器的 loadClass 在他的父类 WebappClassLoaderBase 中:
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class<?> clazz = null;
        //1. 先在本地cache查找该类是否已经加载过
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        //2. 从系统类加载器的cache中查找是否加载过
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        // 3. 尝试用ExtClassLoader类加载器类加载(ExtClassLoader 遵守双亲委派,ExtClassLoader 会使用 Bootstrap ClassLoader 对类进行加载)
        ClassLoader javaseLoader = getJavaseClassLoader();
        try {
            clazz = javaseLoader.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 4. 尝试在本地目录搜索class并加载
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 5. 尝试用系统类加载器(AppClassLoader)来加载
        try {
            clazz = Class.forName(name, false, parent);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
     }
    //6. 上述过程都加载失败,抛出异常
    throw new ClassNotFoundException(name);
}

  WebAppClassLoader 的 findClass() 方法源码:

public Class<?> findClass(String name) throws ClassNotFoundException {
    // Ask our superclass to locate this class, if possible
    // (throws ClassNotFoundException if it is not found)
    Class<?> clazz = null;
 
    // 先在自己的 Web 应用目录下查找 class
    clazz = findClassInternal(name);
 
    // 找不到 在交由父类来处理
    if ((clazz == null) && hasExternalRepositories) {  
        clazz = super.findClass(name);
    }
    if (clazz == null) {
         throw new ClassNotFoundException(name);
    }
    return clazz;
}
  • 为什么tomcat要实现自己的类加载机制
      WebAppClassLoader 加载类的时候,故意打破了JVM 双亲委派机制,绕开了 AppClassLoader,直接先使用 ExtClassLoader 来加载类。最主要原因是保证部署在同一个 Web 容器上的不同 Web 应用程序所使用的类库可以实现相互隔离,避免不同项目的相互影响。当然还有其他原因,如:

  1)保证 Web 容器自身的安全不受部署的 Web 应用程序影响,所以 Tomcat 使用的类库要与部署的应用的类库相互独立。
  2)保证部分基础类不会被同时加载,有些类库 Tomcat 与部署的应用可以共享,比如说 servlet-api。
  3)保证部署在同一个 Web 容器的应用之间的类库可以共享,这听起来好像主要原因相互矛盾,但其实这很合理,类被类加载器加载到虚拟机后,会存放在方法区的永久代中,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。比如这时候如果有大量的应用使用 spring 来管理,如果 spring 类库不能共享,那每个应用的 spring 类库都会被加载一次,将会是很大的资源浪费。

  小结:Tomcat 实际上只有 WebAppClassLoader 加载器中打破了双亲委派,其他类加载器还是遵循双亲委派的。 这样做最主要原因是保证同个 Web 容器中的不同 Web 应用程序所使用的类库相互独立,避免相互影响。

四、Tomcat常见问题

4.1 Tomcat的缺省端口是多少,怎么修改*

  默认是8080端口。以Tomcat7为例,其目录结构示例:

  修改方式:找到Tomcat目录下的conf文件夹,进入conf文件夹里面找到server.xml文件,在server.xml文件里面找到下列信息, 把Connector标签的8080端口改成你想要的端口:

<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" />
  • 在SpringBoot中的修改方式
      server.port是Spring Boot框架中用于配置应用程序端口号的属性。若使用SpringBoot框架,可以在配置文件或Apollo中配置该属性,示例:
    server.port = 38077

4.2 Tomcat Connector有几种运行模式

  Tomcat Connector的三种运行模式,其配置项是在conf/server.xml文件中进行配置。

  • 1、BIO:同步并阻塞
      传统的Java I/O操作,同步且阻塞IO。
      一个线程处理一个请求。缺点:并发量高时,线程数较多,浪费资源。Tomcat7或以下,在Linux系统中默认使用这种方式。该模式性能最差,没有经过任何优化处理和支持。
      配置项:protocol="HTTP/1.1"
      maxThreads="150"。Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数。默认值200。可以根据机器的时期性能和内存大小调整,一般可以在400-500。最大可以在800左右。
      minSpareThreads="25"。Tomcat初始化时创建的线程数。默认值 4。如果当前没有空闲线程,且没有超过maxThreads,一次性创建的空闲线程数量。Tomcat 初始化时创建的线程数量也由此值设置。
      maxSpareThreads="75"。一旦创建的线程超过这个值,Tomcat 就会关闭不再需要的socket线程。默认值50。一旦创建的线程超过此数值,Tomcat会关闭不再需要的线程。线程数可以大致上用 “同时在线人数每秒用户操作次数系统平均操作时间” 来计算。
      acceptCount="100"。指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。默认值10。如果当前可用线程数为0,则将请求放入处理队列中。这个值限定了请求队列的大小,超过这个数值的请求将不予处理。
      connectionTimeout="20000" 。网络连接超时,默认值20000,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。
  • 2、NIO:同步非阻塞IO
      利用Java的异步IO处理,可以通过少量的线程处理大量的请求,可以复用同一个线程处理多个connection(多路复用)
      是Java SE 1.4及后续版本提供的一种新的I/O操作方式(即java.nio包及其子包)。Java nio是一个基于缓冲区、并能提供非阻塞I/O操作的Java API,因此nio也被看成是non-blocking I/O的缩写。它拥有比BIO更好的并发运行性能。
      Tomcat8在Linux系统中默认使用这种方式。Tomcat7必须修改Connector配置来启动
      配置项:protocol="org.apache.coyote.http11.Http11NioProtocol"
      备注:我们常用的Jetty,Mina,ZooKeeper等都是基于java nio实现。
  • 3、APR(Apache Portable Runtime/Apache可移植运行时库)
      Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作,从而大大地提高Tomcat对静态文件的处理性能。从操作系统级别来解决异步的IO问题,大幅度的提高性能。
      Tomcat apr也是在Tomcat上运行高并发应用的首选模式
      配置项:protocol="org.apache.coyote.http11.Http11AprProtocol"
      备注:需在本地服务器安装APR库。Tomcat7或Tomcat8在Win7或以上的系统中启动默认使用这种方式。Linux如果安装了apr和native,Tomcat直接启动就支持apr。

4.3 Tomcat有几种部署方式

  在Tomcat中部署Web应用的方式:

  • 1、利用Tomcat的自动部署
      直接将Web项目文件(一般是复制生成的war包)复制到Tomcat的webapps目录中,启动Tomcat,即可访问。
  • 2、修改conf/server.xml文件部署
      修改conf/server.xml文件,在<host>节点增加Context节点可以部署应用。示例:
  • 3、增加自定义的Web部署文件
      在conf/Catalina/localhost/路径下,增加一个任意名称的xml文件,只要和当前的目录下的其他xml文件不重名就行。其内容是Context节点, 可以部署应用。内容示例:
<Context path= "/hello" docBase = "E:\\Testj2ee\web" debug="0" privileged="true">
</Context>

  第3种方式有个优点:可以定义别名。服务器运行的项目名称是path对应的值,外部访问的url是xml的文件名。这种方式隐藏了项目的名称,对一些项目名称被固定不能更换,但外部访问时又想换个路径的场景,是有用的。

  第1种方式常用于实际开发中,将项目打包时生成的war/jar文件,存放到服务器上的指定目录中;第2种和第3种方式可用于自己本地开发一些非正式项目。

4.4 怎么在Linux上安装Tomcat

  1. 先去下载Tomcat的安装包;
  2. 上传到Linux上,解压;
  3. 修改端口号,也可以不修改;
  4. 修改好了之后,你就进入你这个tomcat下的bin目录, 执行 ./startup.sh ,这样就启动成功。

4.5 怎么在Linux部署项目

  先使用eclipse或IDEA把项目打成.war包,然后上传到Linux服务器,然后把项目放在Tomcat的bin目录下的webapps,在重启Tomcat就行。

4.6 Tomcat的目录结构

  /bin:存放用于启动和暂停Tomcat的脚本
  /conf:存放Tomcat的配置文件
  /lib:存放Tomcat服务器需要的各种jar包。
  /logs:存放Tomcat的日志文件。
  /temp:Tomcat运行时用于存放临时文件。
  /webapps:web应用的发布目录
  /work:Tomcat把有jsp生成Servlet放于此目录下。

4.7 findClass与loadClass的区别

  findClass用于写类加载逻辑,loadClass方法的逻辑:如果父类加载器加载失败则会调用自己的findClass方法完成加载,保证了双亲委派规则。
  1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可。
  2、如果想打破双亲委派模型,那么就重写整个loadClass方法。

五、Tomcat调优

5.1 参数调优示例

  server.xml中部分参数默认值:

<Connector port="8080" protocol="HTTP/1.1"
	connectionTimeout="20000"
	redirectPort="8443" />

  调整后:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
	connectionTimeout="20000"
	redirectPort="8443"
	executor="TomcatThreadPool"
	enableLookups="false"
	acceptCount="100"
	maxPostSize="10485760"
	compression="on"
	disableUploadTimeout="true"
	compressionMinSize="2048"
	noCompressionUserAgents="gozilla, traviata"
	acceptorThreadCount="2"
	compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/jav
ascript"
	URIEncoding="utf-8"/>

  线程池的相关设置:

<Executor name="TomcatThreadPool" namePrefix="catalina-exec-"
	maxThreads="150" minSpareThreads="100"
	prestartminSpareThreads="true" maxQueueSize="100"/>
  • protocol
      Tomcat8设置nio2更好:org.apache.coyote.http11.Http11Nio2Protocol(如果这个用不了,就用下面那个)。
      Tomcat6、7设置nio更好:org.apache.coyote.http11.Http11NioProtocol
      apr:调用httpd核心链接库来读取或文件传输,从而提高 tomat 对静态文件的处理性能。Tomcat APR 模式也是Tomcat在高并发下的首选运行模式。
  • URIEncoding
      设置为"UTF-8",可以使得Tomcat可以解析含有中文名的文件的url。
    minSpareThreads
      类似于corePoolSize,最小备用线程数,Tomcat启动时的初始化的线程数。
  • maxThreads
      Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数,即最大并发数。默认设置200,一般建议在500 ~ 800,根据硬件设施和业务来判断。
  • maxQueueSize
      指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理,默认设置100。
  • connectionTimeout
      网络连接超时时间毫秒数。
  • enableLookups
      enableLookups=“false”,为了消除DNS查询对性能的影响,可以关闭DNS查询,方式是修改server.xml文件中的enableLookups参数值。
  • maxConnections
      这个值表示最多可以有多少个socket连接到Tomcat上。NIO模式下默认是10000。当连接数达到最大值后,系统会继续接收连接但不会超过acceptCount的值。
  • acceptorThreadCount
      用于接收连接的线程的数量,默认值是1。一般这个指需要改动的时候是因为该服务器是一个多核CPU,如果是多核CPU一般配置为 2。
  • HTTP压缩相关的配置
      示例:

compression=“on” compressionMinSize=“2048”
compressableMimeType=“text/html,text/xml,text/javascript,text/css,text/plain”

  HTTP压缩可以大大提高浏览网站的速度,它的原理是,在客户端请求网页后,从服务器端将网页文件压缩,再下载到客户端,由客户端的浏览器负责解压缩并浏览。相对于普通的浏览过程 HTML,CSS,Javascript ,Text,它可以节省 40%左右的流量。更为重要的是,它可以对动态生成的,包括CGI、PHP、JSP、ASP、Servlet、SHTML等输出的网页也能进行压缩。
  优化配置示例:

  1. compression=“on” 打开压缩功能。
  2. compressionMinSize=“2048” 启用压缩的输出内容大小,这里面默认为 2KB。
  3. noCompressionUserAgents=“gozilla, traviata” 对于以下的浏览器,不启用压缩。
  4. compressableMimeType=“text/html,text/xml” 压缩类型。

5.2 【Tomcat调优思路】

  • 1、有可能的条件下增加服务器的CPU、内存、硬盘等硬件性能
      内存足够并且划分合理的话,可以减少full GC的频率,提高性能。
      硬盘主要影响读写性能,当大量文件读写时,磁盘容易成为性能瓶颈,最好的方法是利用缓存。
  • 2、利用缓存和压缩
      对于静态页面,最好是能够缓存起来,这样就不必每次从磁盘上读,比如可以使用Nginx作为缓存服务器。将图片、css、js文件进行缓存,可以有效地减少对Tomcat的访问。同时,为了能加快网络传输速度,开启gzip压缩也是必不可少的,可以压缩的文件有:文本、图片等。
  • 3、采用集群
      单个服务器的性能总是有限的,最好的办法自然是横向扩展,组建Tomcat集群是有效提升性能的手段。
  • 4、优化Tomcat参数

5.3 Tomcat参数优化

5.3.1 优化连接配置

  以Tomcat7的参数配置为例,需要修改conf/server.xml文件,修改连接相关的参数:
  maxSpareThreads : 如果空闲状态的线程数多于设置的数目,则将这些线程中止,减少这个池中的线程总数。
  minSpareThreads : 最小备用线程数,Tomcat启动时的初始化的线程数。
  connectionTimeout : connectionTimeout为网络连接超时时间毫秒数。通常可设置为 30000 毫秒。
  maxThreads : Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数,即最大并发数。
  acceptCount : 允许的最大连接数,acceptCount是当线程数达到maxThreads后,后续请求会被放入一个等待队列,这个acceptCount是这个队列的大小,如果这个队列也满了,就直接refuse connection。acceptCount应大于等于maxProcessors,默认值为100。
  minProcessors:最小空闲连接线程数,用于提高系统处理性能,默认值为10。此属性表示服务器启动时创建的处理请求的线程数应该足够处理一个小量的负载。也就是说,如果一天内每秒仅发生5次单击事件,并且每个请求任务处理需要1秒钟,那么预先设置线程数为5就足够。
  maxProcessors:最大连接线程数,即:并发处理的最大请求数,默认值为75。

  其中和最大连接数相关的参数为maxProcessors和acceptCount。如果要加大并发连接数,应同时加大这两个参数。
  Web服务器允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000个左右,Linux是1000个左右。

5.3.2 优化JVM参数

  内存方式的设置是在catalina.sh中,调整一下JAVA_OPTS 变量即可,因为后面的启动参数会把JAVA_OPTS作为JVM的启动参数来处理。
  具体设置示例:

JAVA_OPTS="$JAVA_OPTS -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4"

  参数含义:
  -Xmx3550m:设置JVM最大可用堆内存为3550M。当应用程序需要的内存超出堆的最大内存值时虚拟机就会提示内存溢出,并导致应用程序崩溃,一般建议堆的最大值设置为可用内存的最大值的80%。
  -Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx 相同,以避免每次垃圾回收完成后 JVM 重新分配内存。
  -Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 +
持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐年轻代配置为整个堆的 3/8
  -Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
  -XX:NewRatio=4:设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5。
  -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个 Survivor 区占整个年轻代的1/6。
  -XX:PermSize=16m:设置持久代初始大小为 16m。
  -XX:MaxPermSize=128m:设置持久代最大大小为 128m。

PermGen space指内存的永久保存区域,主要存放的是Class和Meta信息,Class被加载时就会被存放在该区域中。如果应用中有很多class时,有可能会出现OutOfMemoryError:PermGen space。比如引用了大量的第三方jar,其大小超过了jvm默认的大小(4M),就会产生该错误。该问题的解决方法,就是修改PermSize和MaxPermSize参数。

  -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。

5.3.3 禁用DNS查询

  当Web应用程序向要记录客户端的信息时,它也会记录客户端的IP地址或者通过域名服务器查找机器名转换为IP地址。DNS查询需要占用网络,并且包括可能从很多很远的服务器或者不起作用的服务器上去获取对应的 IP 的过程,这样会消耗一定的时间。为了消除DNS查询对性能的影响我们可以关闭DNS查询,方式是修改server.xml文件中的enableLookups参数值。示例:

<!--Tomcat4-->
<Connector
	className="org.apache.coyote.tomcat4.CoyoteConnector"port="80"
	minProcessors="5"maxProcessors="75"enableLookups="false"redirectPort="8443"
	acceptCount="100"debug="0"connectionTimeout="20000"
	useURIValidationHack="false"disableUploadTimeout="true"/>

<!--Tomcat5-->
<Connector 
	port="80"maxThreads="150"minSpareThreads="25"
	maxSpareThreads="75"enableLookups="false"redirectPort="8443"
	acceptCount="100"debug="0"connectionTimeout="20000"
	disableUploadTimeout="true"/>

5.4 Tomcat内存相关问题的解决方法

  • 1、OutOfMemoryError:Java heap space
      出现这种问题,是Java的堆内存设置太小导致的。这个问题大致有两种解决方式。
    1、设置环境变量
      修改 TOMCAT_HOME/bin/catalina.sh,比如设置为:set JAVA_OPTS=-Xms32m-Xmx512m。
    2、在启动项目的命令上加上JVM参数
      比如在启动命令上加上:-Xms32m-Xmx800m。
  • 2、OutOfMemoryError:PermGen space
      永久代空间不足,永久代主要保存的是Class和Meta信息。如果应用中有很多class的话,就可能出现这个错。
      解决方法可以修改该区域内存大小,比如修改 TOMCAT_HOME/bin/catalina.sh中的内容:JAVA_OPTS="-server-XX:PermSize=64M-XX:MaxPermSize=128m。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值