12 | 实战:优化并提高Tomcat启动速度

到目前为止,我们学习了Tomcat和Jetty的整体架构,还知道了Tomcat是如何启动起来的,今天我们来聊一个比较轻松的话题:如何优化并提高Tomcat的启动速度。

我们在使用Tomcat时可能会碰到启动比较慢的问题,比如我们的系统发布新版本上线时,可能需要重启服务,这个时候我们希望Tomcat能快速启动起来提供服务。其实关于如何让Tomcat启动变快,官方网站有专门的文章来介绍这个话题。下面我也针对Tomcat 8.5和9.0版本,给出几条非常明确的建议,可以现学现用。

清理你的Tomcat

1. 清理不必要的Web应用

首先我们要做的是删除掉webapps文件夹下不需要的工程,一般是host-manager、example、doc等这些默认的工程,可能还有以前添加的但现在用不着的工程,最好把这些全都删除掉。如果你看过Tomcat的启动日志,可以发现每次启动Tomcat,都会重新布署这些工程。

2. 清理XML配置文件

我们知道Tomcat在启动的时候会解析所有的XML配置文件,但XML解析的代价可不小,因此我们要尽量保持配置文件的简洁,需要解析的东西越少,速度自然就会越快。

3. 清理JAR文件

我们还可以删除所有不需要的JAR文件。JVM的类加载器在加载类时,需要查找每一个JAR文件,去找到所需要的类。如果删除了不需要的JAR文件,查找的速度就会快一些。这里请注意:Web应用中的lib目录下不应该出现Servlet API或者Tomcat自身的JAR,这些JAR由Tomcat负责提供。如果你是使用Maven来构建你的应用,对Servlet API的依赖应该指定为<scope>provided</scope>

4. 清理其他文件

及时清理日志,删除logs文件夹下不需要的日志文件。同样还有work文件夹下的catalina文件夹,它其实是Tomcat把JSP转换为Class文件的工作目录。有时候我们也许会遇到修改了代码,重启了Tomcat,但是仍没效果,这时候便可以删除掉这个文件夹,Tomcat下次启动的时候会重新生成。

禁止Tomcat TLD扫描

Tomcat为了支持JSP,在应用启动的时候会扫描JAR包里面的TLD文件,加载里面定义的标签库,所以在Tomcat的启动日志里,你可能会碰到这种提示:

At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.

Tomcat的意思是,我扫描了你Web应用下的JAR包,发现JAR包里没有TLD文件。我建议配置一下Tomcat不要去扫描这些JAR包,这样可以提高Tomcat的启动速度,并节省JSP编译时间。

那如何配置不去扫描这些JAR包呢,这里分两种情况:

  • 如果你的项目没有使用JSP作为Web页面模板,而是使用Velocity之类的模板引擎,你完全可以把TLD扫描禁止掉。方法是,找到Tomcat的conf/目录下的context.xml文件,在这个文件里Context标签下,加上JarScannerJarScanFilter子标签,像下面这样。

  • 如果你的项目使用了JSP作为Web页面模块,意味着TLD扫描无法避免,但是我们可以通过配置来告诉Tomcat,只扫描那些包含TLD文件的JAR包。方法是,找到Tomcat的conf/目录下的catalina.properties文件,在这个文件里的jarsToSkip配置项中,加上你的JAR包。
tomcat.util.scan.StandardJarScanFilter.jarsToSkip=xxx.jar

关闭WebSocket支持

Tomcat会扫描WebSocket注解的API实现,比如@ServerEndpoint注解的类。我们知道,注解扫描一般是比较慢的,如果不需要使用WebSocket就可以关闭它。具体方法是,找到Tomcat的conf/目录下的context.xml文件,给Context标签加一个containerSciFilter的属性,像下面这样。

更进一步,如果你不需要WebSocket这个功能,你可以把Tomcat lib目录下的websocket-api.jartomcat-websocket.jar这两个JAR文件删除掉,进一步提高性能。

关闭JSP支持

跟关闭WebSocket一样,如果你不需要使用JSP,可以通过类似方法关闭JSP功能,像下面这样。

我们发现关闭JSP用的也是containerSciFilter属性,如果你想把WebSocket和JSP都关闭,那就这样配置:

禁止Servlet注解扫描

Servlet 3.0引入了注解Servlet,Tomcat为了支持这个特性,会在Web应用启动时扫描你的类文件,因此如果你没有使用Servlet注解这个功能,可以告诉Tomcat不要去扫描Servlet注解。具体配置方法是,在你的Web应用的web.xml文件中,设置<web-app>元素的属性metadata-complete="true",像下面这样。

metadata-complete的意思是,web.xml里配置的Servlet是完整的,不需要再去库类中找Servlet的定义。

配置Web-Fragment扫描

Servlet 3.0还引入了“Web模块部署描述符片段”的web-fragment.xml,这是一个部署描述文件,可以完成web.xml的配置功能。而这个web-fragment.xml文件必须存放在JAR文件的META-INF目录下,而JAR包通常放在WEB-INF/lib目录下,因此Tomcat需要对JAR文件进行扫描才能支持这个功能。

你可以通过配置web.xml里面的<absolute-ordering>元素直接指定了哪些JAR包需要扫描web fragment,如果<absolute-ordering/>元素是空的, 则表示不需要扫描,像下面这样。

随机数熵源优化

这是一个比较有名的问题。Tomcat 7以上的版本依赖Java的SecureRandom类来生成随机数,比如Session ID。而JVM 默认使用阻塞式熵源(/dev/random), 在某些情况下就会导致Tomcat启动变慢。当阻塞时间较长时, 你会看到这样一条警告日志:

<DATE> org.apache.catalina.util.SessionIdGenerator createSecureRandom
INFO: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [8152] milliseconds.

这其中的原理我就不展开了,你可以阅读资料获得更多信息。解决方案是通过设置,让JVM使用非阻塞式的熵源。

我们可以设置JVM的参数:

 -Djava.security.egd=file:/dev/./urandom

或者是设置java.security文件,位于$JAVA_HOME/jre/lib/security目录之下: securerandom.source=file:/dev/./urandom

这里请你注意,/dev/./urandom中间有个./的原因是Oracle JRE中的Bug,Java 8里面的 SecureRandom类已经修正这个Bug。 阻塞式的熵源(/dev/random)安全性较高, 非阻塞式的熵源(/dev/./urandom)安全性会低一些,因为如果你对随机数的要求比较高, 可以考虑使用硬件方式生成熵源。

并行启动多个Web应用

Tomcat启动的时候,默认情况下Web应用都是一个一个启动的,等所有Web应用全部启动完成,Tomcat才算启动完毕。如果在一个Tomcat下你有多个Web应用,为了优化启动速度,你可以配置多个应用程序并行启动,可以通过修改server.xml中Host元素的startStopThreads属性来完成。startStopThreads的值表示你想用多少个线程来启动你的Web应用,如果设成0表示你要并行启动Web应用,像下面这样的配置。

这里需要注意的是,Engine元素里也配置了这个参数,这意味着如果你的Tomcat配置了多个Host(虚拟主机),Tomcat会以并行的方式启动多个Host。

本期精华

今天我讲了不少提高优化Tomcat启动速度的小贴士,现在你就可以把它们用在项目中了。不管是在开发环境还是生产环境,你都可以打开Tomcat的启动日志,看看目前你们的应用启动需要多长时间,然后尝试去调优,再看看Tomcat的启动速度快了多少。

如果你是用嵌入式的方式运行Tomcat,比如Spring Boot,你也可以通过Spring Boot的方式去修改Tomcat的参数,调优的原理都是一样的。

课后思考

在Tomcat启动速度优化上,你都遇到了哪些问题,或者你还有自己的“独门秘籍”,欢迎把它们分享给我和其他同学。

不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。

精选留言

  • 王盛武

    2019-06-09 19:36:20

    调大vm xms xmx避免反复扩容堆内存
    换上固态硬盘可以提速xml文件读取
    server.xml去掉监听
    去掉不要的ajp
    去掉多余的连接器
    线程池的核心线程设置延迟初始化
    去掉access log,因为nginx里已有access log
    减少项目里多余的jar
    精确设置mvc注解的包扫描范围
    xml spring bean设置延迟初始化
    数据库连接池初始化数量减少
    作者回复

    👍

    2019-06-10 16:07:59

  • 刘冬

    2019-06-06 00:14:08

    请问老师,对于SpringBoot内嵌的Tomcat,怎么来优化呢?
    作者回复

    在Springboot里配置文章里提到的那些参数,比如:
    server.tomcat.additional-tld-skip-patterns: xxx*.jar

    或者通过TomcatServletWebServerFactory来修改参数
    @Bean
    public TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {
    @Override
    protected void postProcessContext(Context context) {
    ((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
    }
    };
    }

    2019-06-06 07:31:08

  • Shine

    2019-06-08 16:06:36

    老师,这种tomcat启动优化很少用到吧。貌似很多人都不太关心tomcat启动优化
    作者回复

    重启和部署服务的时候,启动快的话能减少downtime。

    2019-06-08 21:30:19

  • 君哥聊技术

    2019-06-06 09:08:43

    startStopThreads 的值表示你想用多少个线程来启动你的 Web 应用,如果设成 0 表示你要并行启动 Web 应用,像下面这样的配置。
    startStopThreads=0默认会用多少个线程呢?是会用系统所有能调度的线程吗?
    作者回复

    Server 有一个专门的线程池来叫做utilityExecutor,来跑这些任务,在这个线程池创建startStopThreads个数的线程。默认是2个。

    2019-06-07 19:05:39

  • 小胖

    2020-07-03 19:11:02

    双哥能出个netty或者dubbo专栏吗😍
  • lulu

    2020-09-20 20:59:58

    请教一个问题。我对应用进行服务器的迁移,从上海机房迁移北京机房。发现Tomcat启动变得非常慢。原来在上海机房部署应用的Tomcat启动20482 ms,现在在北京机房216643 ms,时间变成了10倍。已经排除了redis、mysql连接的问题。也不是安全随机数慢的问题。通过strace、jstack也没有找到什么原因。请问老师我改怎么调查,怎么解决。谢谢。
  • vvsuperman

    2019-06-17 14:13:05

    压测的时8c8g,做的mock请求(空请求,立即返回),并发500 tomcat 8 tps才600,如何提高tps呢?
    作者回复

    空请求没有IO等待,可以试试把线程池的线程数调小一点,减少线程切换开销,看有没有帮助

    2019-06-19 00:12:11

  • 小呆娃

    2019-06-09 20:14:22

    老师,请教您一个问题,tomcat启动的时候卡在loadClass,这个一般是什么问题呢?能给个排查的思路吗?谢谢老师
    作者回复

    用jstack看堆栈信息看具体卡在哪一行。

    2019-06-10 16:10:24

  • Visual C++

    2019-06-12 21:55:37

    我的环境是docker centos tomcat8,按你设置,还要20妙启动
  • 2019-06-12 14:31:12

    为什么要删除logs下不需要的日志文件
    作者回复

    文件越大,占磁盘空间

    2019-06-12 18:51:45

  • 困兽

    2023-05-21 19:38:02

    我有个问题。像JSP和websocket这种要扫描所有jar的逻辑应该只要扫描一次就可以获得它们两个功能要获得的东西了。不需要每个功能都扫描一遍。那如果是这样的话。只关闭JSP功能是不是没用。因为websocket功能还是需要扫描所有jar
  • 边宸

    2021-03-30 11:23:38

    老师你用的版本是什么啊,我网上查了一下配置tld扫描是在context标签里面直接配processtlds=false
  • 右耳朵猫咪

    2021-03-11 20:49:21

    老师,tomcat因为hikari连接超时而宕机是怎么回事儿呢?
  • Vainycos

    2019-09-01 12:57:37

    老师您好,在提高tomcat启动效率的方法中,您提到可以删除不必要的默认应用:host-manager/examples/docs。但是默认的应用了还有ROOT以及manager,请问这两个应用是不能随便删的是吗,分别有他各自的作用还是会影响到部署的应用,请教一下老师。
  • xj_zh

    2019-07-23 10:26:53

    老师,可以把每一讲的资料单独整理成一片文章呢吗,这样方便快速查找。
    比如:
    tomcat的源码连接,推荐阅读的一些资料。谢谢!
  • 新世界

    2019-06-14 08:36:00

    关于session ID的生成,tomcat为什么不默认指定采用非阻塞模式生成?
  • Visual C++

    2019-06-12 22:07:12

    有没有更大优化空间?
  • QQ怪

    2019-06-06 15:21:15

    学到了