Internal and External Performance Tuning with Tomcat

In this fourth part of a five-part series on performance tuning with Tomcat, you will learn how to tune both Tomcat and non-Tomcat components for performance. This article is excerpted from chapter four of Tomcat: The Definitive Guide, Second Edition, written by Jason Brittain and Ian F. Darwin (O’Reilly; ISBN: 0596101066). Copyright © 2008 O’Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O’Reilly Media.

External Tuning

Once you’ve got an idea how your application and Tomcat instance respond to load, you can begin some performance tuning. There are two basic categories of tuning detailed here:

External tuning
  
Tuning that involves non-Tomcat components, such
   as the operating system that Tomcat runs on and the
   Java virtual machine running Tomcat.

Internal tuning
  
Tuning that deals with Tomcat itself, ranging from
   changing settings in configuration files to modifying
   the Tomcat source code. Modifications to your web
   application also fall into this category.

In this section, we detail the most common areas of external tuning, and then move on to internal tuning in the next section.

JVM Performance

Tomcat doesn’t run directly on a computer; there is a JVM and an operating system between it and the underlying hardware. There are relatively few complete and fully compatible Java virtual machines to choose from for any given operating system and architecture combination, so most people will probably stick with Sun’s or their own operating system vendor’s implementation.

If your goal is to run the fastest Java runtime and squeeze the most performance out of your webapp, you should benchmark Tomcat and your webapp on each of the Java VMs that are available for your hardware and operating system combination. Do not assume that the Sun Java VM is going to be the fastest because that is often not the case (at least in our experience). You should try other brands and even different major version numbers of each brand to see what runs your particular webapp fastest.

If you choose just one version of the Java class file format that JVMs you use must support (for example, you want to compile your webapp for Java 1.6 JVMs), you can benchmark each available JVM brand that supports that level of the bytecodes, and choose one that best fits your needs. For instance, if you choose Java 1.6, you could benchmark Sun’s 1.6 versus IBM’s 1.6 versus BEA’s 1.6. One of these will run Tomcat and your webapp the fastest. All of these brands are used in production by a large number of users and are targeted at slightly different user bases. See Appendix A for information about some of the JDKs that may be available for your operating system.

As a generic example of performance improvements between major versions of one JVM brand, a major version upgrade could buy you a 10 percent performance increase. That is, upgrading from a Java 1.5 JVM to a Java 1.6 JVM your webapp may run 10 percent faster, without changing any code in it whatsoever. This is a ballpark figure, not a benchmark result; your mileage may vary, depending on the brands and versions you test and what your webapp does.

It is likely true that newer JVMs have both better performance and less stability, but the longer a major version of the JVM has been released as a final/stable version, the less you have to worry about its stability. A good rule of thumb is to get the latest stable version of the software, except when the latest stable version is the first or second stable release of the next major version of the software. For example, if the latest stable version is 1.7.0, you may opt for 1.6.29 instead if it is more stable and performs well enough.

It is often the case that people try to modify the JVM startup switches to make their Tomcat JVM serve their webapp’s pages faster. This can help, but does not usually yield a high percentage increase in performance. The main reason it does not help much: the JVM vendor did their own testing before releasing the JDK, found which settings yield the best performance, and made those settings the defaults.

If you change a JVM startup switch to activate a setting that is not the default, chances are that you will slow down your JVM. You have been warned! But, in case you would like to see which Sun JVM settings you could change, have a look at http://www.md.pp.ru/~eu/ jdk6options.html .

One exception here is the JVM’s heap memory allocation. By default, vendors choose for the JVM to start by allocating a small amount of memory (32 MB in the Sun JVM’s case), and if the Java application requires more memory, the JVM’s heap size is reallocated larger while the application is paused. The JVM may do this a number of times in small memory increments before it hits a heap memory size ceiling. Because the application is paused each time the heap size is increased, performance suffers. If that is happening while Tomcat is serving a webapp’s pages, the page responses will appear to take far longer than normal to all web clients whose requests are outstanding at the time the pause begins. To avoid these pauses, you can set the minimum heap size and the maximum heap size to be the same. That way, the JVM will not attempt to expand the heap size during runtime. To do this to Tomcat’s JVM startup switches, just set the JAVA_OPTS environment variable to something such as
-Xms512M -Xmx512M . (This means that the maximum and minimum heap size should be set to 512 MB.) Set the size to an appropriate value on your machine, based on how much memory it has free after it boots.

You can also try benchmarking different garbage collection algorithm settings, however, as we stated earlier you may find that the default settings are always fastest. You never know until you benchmark it, though. Check the documentation for the JVM you’re benchmarking to find the startup switch that will enable a different garbage collection algorithm because these settings are JVM implementation-specific. Again, you’ll want to set it in JAVA_OPTS to get Tomcat to start the JVM that way.

{mospagebreak title=Operating System Performance}

And what about the OS? Is your server operating system optimal for running a large, high-volume web server? Of course, different operating systems have very different design goals. OpenBSD, for example, is aimed at security, so many of the limits in the kernel are set small to prevent various forms of denial-of-service attacks (one of OpenBSD’s mottoes is “Secure by default”). These limits will most likely need to be increased to run a busy web server.

Linux, on the other hand, aims to be easy to use, so it comes with the limits set higher. The BSD kernels come out of the box with a “generic” kernel, that is, most of the drivers are statically linked in. This makes it easier to get started, but if you’re building a custom kernel to raise some of those limits, you might as well rip out unneeded devices. Linux kernels have most of the drivers dynamically loaded. On the other hand, memory itself is getting cheaper, so the reasoning that led to loadable device drivers is less important. What is important is to have lots and lots of memory and to make a lot of it available to the server.

Memory is cheap these days, but don’t buy cheap memory—brand name memory costs only a little more and repays the cost in reliability.

If you run any variant of Microsoft Windows, be sure you have the server version (e.g., Windows Vista Server instead of just Windows Vista Pro). In other nonserver versions, the end user license agreement and/or the operating system’s code itself may restrict the number of users, or the number of network connections that you can use, or place other restrictions on what you can run. Additionally, be sure you obtain the latest Microsoft service packs frequently, for the obvious security reasons (this is true for any system, but is particularly important for Windows).

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.

{mospagebreak title=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.

{mospagebreak title=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.

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/
  javaee/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.

Google+ Comments

Google+ Comments