Internal Tuning

This section details a specific set of techniques that will help your Tomcat instance run faster, regardless of the operating system or JVM you are using. In many cases, you may not have control of the OS or JVM on the machine you are deploying to. In those situations, you should still make recommendations in line with what was detailed in the last section; however, you still should be able to affect changes in Tomcat itself. Here is where we think are the best places to start internally tuning Tomcat.

Disabling DNS Lookups

When a web application wants to log information about the client, it can either log the client's numeric IP address or look up the actual host name in the Domain Name Service data. DNS lookups require network traffic, involving a round-trip response from multiple servers, possibly far away and possibly inoperative, resulting in delays. To disable these delays you can turn off DNS lookups. Then, whenever a web application calls the getRemoteHost( ) method in the HTTP request object, it will only get the numeric IP address. This is set in the Connector object for your application, in Tomcat's server.xml file. For the common java.io HTTP 1.1 connector, use the enableLookups attribute. Just find this part of the server.xml file:

    <!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
    <Connector port="8080" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="true"
 redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true" />

Just change the enableLookups value from "true" to "false", and restart Tomcat. No more DNS lookups and their resulting delays!

Unless you need the fully qualified hostname of every HTTP client that connects to your site, we recommend turning off DNS lookups on production sites. Remember that you can always look up the names later, outside of Tomcat. Not only does turning them off save network bandwidth, lookup time, and memory, but in sites where quite a bit of traffic generates quite a bit of log data, it may save a noticeable amount of disk space as well. For low traffic sites, turning off DNS lookups may not have as dramatic an effect, but it is still not a bad practice. How often have low traffic sites become high traffic sites overnight?

Adjusting the Number of Threads

Another performance control on your application's Connector is the number of request handler threads it uses. By default, Tomcat uses a thread pool to provide rapid response to incoming requests. A thread in Java (as in other programming languages) is a separate flow of control, with its own interactions with the operating system, and its own local memory—but with some memory shared among all threads in the process. This allows developers to provide fine-grained organization of code that will respond well to many incoming requests.

You can control the number of threads that are allocated by changing a Connector's minThreads and maxThreads values. The values provided are adequate for typical installations but may need to be increased as your site gets larger. The minThreads value should be high enough to handle a minimal loading. That is, if at a slow time of day you get five hits per second and each request takes under a second to process, the five preallocated threads are all you will need. Later in the day, as your site gets busier, more threads will need to be allocated (up to the number of threads specified in maxThreads attribute). There needs to be an upper limit to prevent spikes in traffic (or a denial-of-service attack from a malicious user) from bombing out your server by making it exceed the maximum memory limit of the JVM.

The best way to set these to optimal values is to try many different settings for each and test them with simulated traffic loads while watching response times and memory utilization. Every machine, operating system, and JVM combination may act differently, and not everyone's web site traffic volume is the same, so there is no cut-and-dry rule on how to determine minimum and maximum threads.

Speeding Up JSPs

When a JSP is first accessed, it is converted into Java servlet source code, which must then be compiled into Java bytecode.

Tip

Another option is to not use JSPs altogether and take advantage of some of the various Java templating engines available today. While this is obviously a larger scale decision, many have found it worth at least investigating. For detailed information about other templating languages that you can use with Tomcat, see Jason Hunter and William Crawford's Java Servlet Programming (O'Reilly).

Precompiling JSPs by requesting them

Since a JSP is normally compiled the first time it's accessed via the web, you may wish to perform precompilation after installing an updated JSP instead of waiting for the first user to visit it. Doing so helps to ensure that the new JSP works as well on your production server as it did on your test machine.

There is a script file called jspc in the Tomcat bin/ directory that looks as though it might be used to precompile JSPs, but it is not. It does run the translation phase from JSP source to Java source, but not the Java compilation phase, and it generates the resulting Java source file in the current directory, not in the work directory for the web application. It is primarily for the benefit of people debugging JSPs.

The simplest way to ensure precompilation of any given JSP file is to simply access the JSP through a web client. This will ensure the file is translated to a servlet, compiled, and then run. It also has the advantage of exactly simulating how a user would access the JSP, allowing you to see what they would. You can catch any errors, correct them, and then repeat the process. Of course, this development cycle is best done in a development environment, not on the production server.

Precompiling JSPs at webapp start time

Another excellent but seldomly used feature of the Java Servlet Specification is that it specifies that servlet containers must allow webapps to specify JSP page(s) that should be precompiled at webapp start time.

For example, if you want index.jsp (in the root of your webapp's directory) to always be precompiled at webapp startup time, you can add a <servlet> tag for this file in your web.xml file, like this:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/j
avaee/web-app_2_5.xsd"
    version="2.5">

<servlet>
    <servlet-name>index.jsp</servlet-name>
    <jsp-file>/index.jsp</jsp-file>
    <load-on-startup>0</load-on-startup>
  </servlet>

</web-app>

Then, Tomcat will automatically precompile index.jsp for you at webapp start time, and the very first request to /index.jsp will be mapped to the precompiled servlet class file of the JSP.

Configuring precompilation in your webapp this way means that all compilation of the JSPs is done at webapp start time, whether the JSPs are being requested by web clients or not. Each JSP page you declare this way in web.xml will be precompiled. One drawback to this approach is that webapp startup time is then always longer because every page you specify must be precompiled before the webapp is accessible to web clients.

Also, the <load-on-startup> container tag should contain a positive integer value. This is a loose way to specify precompilation order. The lower you set this number on a JSP page, the earlier in the startup process it will be precompiled.

Precompiling your JSPs in this manner may make your JSPs appear faster to the first web client to request each JSP page after a webapp (re)deployment, however, JSPs that are compiled at build time (before deployment) run slightly faster on every request, even after the first request to each JSP page.

Precompiling JSPs at build time using JspC

Here are some valid (as of the time of this writing) reasons for doing build-time precompilation of JSPs:

  • You need all the performance you can squeeze out of your webapp, and build-time compiled JSPs run faster than JSPs that are compiled inside Tomcat after the webapp is deployed. First, the Java class bytecodes generated in both situations should really be the same, and if they're not exactly the same, the difference will be very small—certainly not worth a major deployment change such as is necessary to precompile the JSPs before deployment. Also, the time it takes Tomcat to compile the original JSP is usually small and occurs only on the first request of each JSP page after webapp deployment/redeployment. All other requests to the JSP pages serve from the compiled and loaded JSP servlet class (JSPs are compiled into Java servlets). But since JSPs that were compiled before webapp deployment are mapped to the URI space in the web.xml file, Tomcat is able to route requests to them slightly faster than if the JSP page were compiled at webapp runtime. This is because when JSP pages are compiled during runtime, the resulting servlets must be mapped to the URI space first by the regular URI mapper, which sends the request to the JspServlet, then the request is mapped to the requested JSP page by Tomcat's JspServlet. Note that the runtime compiled JSPs are mapped via two layers of indirection (two distinct mappers), and precompiled JSPs are mapped via only the first layer of indirection. The performance difference comes down to the performance of the two different URI mapper situations. In the end, precompiled JSPs usually run about 4 percent faster. Precompiling them before webapp deployment would save you the small initial request compile time for each JSP page in your webapp, plus the 4 percent performance improvement on each subsequent request for a JSP page. In Tomcat 4.1.x, the runtime JSP request mapper was noticeably slower than the web.xml servlet mapper and made it worth precompiling JSPs before webapp deployment. That made JSP pages faster by approximately 12 percent or so in our tests. But, for Tomcat version 5.0.x and higher, this margin was reduced to about 4 percent or less.

  • By precompiling JSPs at webapp build or packaging time, the syntax for the JSPs is checked during the JSP compilation process, which means that you can be confident that the JSPs at least compile with no syntax errors before you deploy your webapp. This is great a way to avoid the situation where you have deployed your webapp to your production server(s) only to find out later that one of the JSPs had a syntax error, and it was found by the first user who requested that page. Also, finding errors in the development phase of the code allows the developer to find and fix the errors more rapidly; it shortens the development cycle. This will not prevent every kind of bug because a compiled JSP may still have runtime logic bugs, but at least you can catch all syntax errors in the development environment.

  • If you have a large number of JSP files in your webapp, each of which is somewhat long (hopefully you are not copying and pasting lots of content from one JSP page to many other JSP pages; you should instead make use of the JSP include feature), the initial compilation time for all the JSP pages combined could be significantly large. If so, you can save time on the production server by precompiling the JSPs before webapp deployment time. This is especially helpful if your traffic load is high, and your server responses would otherwise slow down quite a bit, while the server is initially compiling many JSP pages at the same time when the webapp is first started.

  • If you have a low server resource situation, for instance, if the Java VM is configured to use a small amount of RAM or the server does not have very many CPU cycles for Tomcat to use, you may not want to do any JSP compilation at all on the server. Instead, you could do the compilation in your development environment and deploy only compiled servlets, which would lighten the utilization of both memory and CPU time for the first request of each JSP file after each new copy of the webapp is deployed.

  • You are developing a JSP web application that you will sell to customer(s) whom you do not want to have the JSP source code. If you could give the customer(s) the webapp containing just compiled servlets, you could develop the webapp using the original JSPs, and ship it with the compiled JSP servlets. In this use case, precompiling before release to the customer is used as a source code obfuscation mechanism. Keep in mind, though, that compiled Java class files are relatively easy to decompile into readable Java source code, but (as of this writing) there is no way to decompile it all the way back into JSP source code.

  • Also, as of Tomcat version 5.5, you no longer need a JDK that has a built-in Java source compiler to serve runtime compiled JSPs. Tomcat versions 5.5 and higher come bundled with the Eclipse JDT compiler, which is a Java compiler that is itself written in pure Java. Because the JDT compiler is bundled as part of Tomcat, Tomcat can always compile JSPs into servlets, even when Tomcat is run on a JRE and not a JDK.

Example 4-4 is an Ant build file that you can use to compile your webapp's JSP files at build time.

Example 4-4. The precompile-jsps.xml Ant build file

<project name="pre-compile-jsps" default="compile-jsp-servlets">

  <!-- Private properties. -->
  <property name="webapp.dir" value="${basedir}/webapp-dir"/>
  <property name="tomcat.home" value="/opt/tomcat"/>
  <property name="jspc.pkg.prefix" value="com.mycompany"/>
  <property name="jspc.dir.prefix" value="com/mycompany"/>

  <!-- Compilation properties. -->
  <property name="debug" value="on"/>
  <property name="debuglevel" value="lines,vars,source"/>
  <property name="deprecation" value="on"/>
  <property name="encoding" value="ISO-8859-1"/>
  <property name="optimize" value="off"/>
  <property name="build.compiler" value="modern"/>
  <property name="source.version" value="1.5"/>

  <!-- Initialize Paths. -->
  <path id="jspc.classpath">
    <fileset dir="${tomcat.home}/bin">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${tomcat.home}/server/lib">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${tomcat.home}/common/i18n">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${tomcat.home}/common/lib">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${webapp.dir}/WEB-INF">
      <include name="lib/*.jar"/>
    </fileset>
    <pathelement location="${webapp.dir}/WEB-INF/classes"/>
    <pathelement location="${ant.home}/lib/ant.jar"/>
    <pathelement location="${java.home}/../lib/tools.jar"/>
  </path>
  <property name="jspc.classpath" refid="jspc.classpath"/>

  <!-- ========================================================== -->
  <!-- Generates Java source and a web.xml file from JSP files.   -->
  <!-- ========================================================== -->
  <target name="generate-jsp-java-src">
    <mkdir dir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"/>
    <taskdef classname="org.apache.jasper.JspC" name="jasper2">
      <classpath>
        <path refid="jspc.classpath"/>
      </classpath>
    </taskdef>
    <touch file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
    <jasper2 uriroot="${webapp.dir}"
             package="${jspc.pkg.prefix}"
             webXmlFragment="${webapp.dir}/WEB-INF/jspc-web.xml"
             outputDir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"
             verbose="1"/>
  </target>

  <!-- ========================================================== -->
  <!-- Compiles (generates Java class files from) the JSP servlet -->
  <!-- source code that was generated by the JspC task.           -->
  <!-- ========================================================== -->
  <target name="compile-jsp-servlets" depends="generate-jsp-java-src">
    <mkdir dir="${webapp.dir}/WEB-INF/classes"/>
    <javac srcdir="${webapp.dir}/WEB-INF/jspc-src"
           destdir="${webapp.dir}/WEB-INF/classes"
           includes="**/*.java"
           debug="${debug}"
           debuglevel="${debuglevel}"
           deprecation="${deprecation}"
           encoding="${encoding}"
           optimize="${optimize}"
           source="${source.version}">
      <classpath>
        <path refid="jspc.classpath"/>
      </classpath>
    </javac>
  </target>

  <!-- ========================================================= -->
  <!-- Cleans any pre-compiled JSP source, classes, jspc-web.xml -->
  <!-- ========================================================= -->
  <target name="clean">
    <delete dir="${webapp.dir}/WEB-INF/jspc-src"/>
    <delete dir="${webapp.dir}/WEB-INF/classes/${jspc.dir.prefix}"/>
    <delete file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
  </target>

</project>

If you put this Ant build xml content into a file named something such as pre-compile-jsps.xml, you can test it alongside any build.xml file you already have, and if you like it, you can merge it into your build.xml.

This build file will find all of your webapp's JSP files, compile them into servlet classes, and generate servlet mappings for those JSP servlet classes. The servlet mappings it generates must go into your webapp's WEB-INF/web.xml file, but it would be difficult to write an Ant build file that knows how to insert the servlet mappings into your web.xml file in a repeatable way every time the build file runs. Instead, we used an XML entity include so that the generated servlet mappings go into a new file every time the build file runs and that servlet mappings file can be inserted into your web.xml file via the XML entity include mechanism. To use it, your webapp's WEB-INF/web.xml must have a special entity declaration at the top of the file, plus a reference to the entity in the content of the web.xml file where you want the servlet mappings file to be included. Here is how an empty servlet 2.5 webapp's web.xml file looks with these modifications:

<!DOCTYPE jspc-webxml [
  <!ENTITY jspc-webxml SYSTEM "jspc-web.xml">
]>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/j
avaee/web-app_2_5.xsd"
    version="2.5">

  <!-- We include the JspC-generated mappings here. -->
  &jspc-webxml;

  <!-- Non-generated web.xml content goes here. -->

</web-app>

Make sure your webapp's web.xml file has the inline DTD (the DOCTYPE tag) all the way at the top of the file and the servlet 2.5 web-app schema declaration below that. Then, wherever you want to insert the generated servlet mappings in your web.xml file, put the entity reference &jspc-webxml;. Remember, the entity reference begins with an ampersand (&), then has the name of the entity, and ends with a semicolon (;).

To use the build file, just edit it and set all of the properties at the top to values that match your setup, and then run it like this:

$ ant -f pre-compile-jsps.xml
Buildfile: pre-compile-jsps.xml

generate-jsp-java-src:
  [jasper2] Sep 27, 2008 10:47:15 PM org.apache.jasper.xmlparser.MyEntityResolver reso
lveEntity
  [jasper2] SEVERE: Invalid PUBLIC ID: null
  [jasper2] Sep 27, 2007 10:47:17 PM org.apache.jasper.JspC processFile
  [jasper2] INFO: Built File: /index.jsp

compile-jsp-servlets:
    [javac] Compiling 1 source file to /home/jasonb/myproject/webapp-dir/WEB-INF/class
es

BUILD SUCCESSFUL
Total time: 7 seconds

Any JSP files you have in your webapp dir will be compiled into servlets, and when you deploy the webapp, the JSP page requests will be mapped to the compiled servlets. Ignore the "SEVERE: Invalid PUBLIC ID: null" message if you get it; it's bogus. If you want to clean out the compiled servlets and their generated Java source and mappings, just execute the clean target like this:

$ ant -f pre-compile-jsps.xml clean

One thing that this build file does not do: remove all of the JSP files in your webapp after compiling them. We didn't want you to accidentally delete your JSP files, so we intentionally left it out. Your own build file should do that before the webapp gets deployed. If you forget and accidentally leave the JSP files in the deployed webapp, none of them should get served by Tomcat because the web.xml file explicitly tells Tomcat to use the compiled servlet classes instead.

Get Tomcat: The Definitive Guide, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.