How To Optimize Tomcat JSP Performance
A common problem faced by Tomcat users who want to prepare their lightweight infrastructure for greater load in the future is finding all the small places Tomcat can be tweaked for better performance. Let's say you've optimized your JVM, configured your thread pools correctly, optimized your application code, and applied load-balancing techniques as necessary - what's next?
If you're stumped as to what other capacity planning improvements you can implement without spending any money on new hardware or implementing servlet templates, a good place to look for some extra bandwidth is your JSP performance. Sometimes overlooked as a target for tune-up, JSP pages can become a liability on high load servers with many different JSP pages that will be accessed concurrently by many different users.
Overloaded servers working hard to compile the JSPs into servlets when they are requested for the first time can cause those couple seconds of slowdown that change the user experience from snappy and pleasant to headache-inducing. No one wants to experience the nerve-wracking sensation of sitting around waiting for dynamic content to load in the middle of a transaction.
In this article, we'll look at some strategies you can use to improve your site's handling of JSP pages. Starting with an overview of the process Tomcat uses to handle JSP code, we'll cover every technique there is for tuning your JSP usage, from a simple audit of your site's dynamic content, to pre-compiling techniques. We'll also go over some best practices you'll want to follow when actually writing your JSP code.
Tcat's web console makes performance tuning a snap. Deep visual diagnostics give you unparalleled insight into your web application performance, while improved deployment capabilities including group deployment speed up your development cycles. Try Tcat for free today!
JSP is a combination of Java servlet code and HTML mark-up. Tomcat handles JSP with an engine called Jasper 2, which consists of various components necessary for handling JSP transactions and parsing, as well as a Java compiler.
The first time a JSP page is requested, Tomcat passes it to Jasper which parses the code and sends it to its Java compiler (JDT, a component borrowed from the Eclipse IDE project) to have the servlet elements compiled into Java bytecode classes. Once initially compiled, these classes are re-used every time the JSP page is requested.
This functionality allows developers to easily generate dynamic content without using a full-fledged applet, but the need to compile the code is also where bottlenecks can appear under load.
The first step towards improved JSP performance is to take a hard analytical look at your site's structure, expected load, and required functionality in relation to your use of JSP pages.
When creating a complicated site architecture with lots of mixed dynamic and static content, sometimes content that is more static than dynamic ends up being handled by JSP, when it could simply be served as regularly updated static content.
A good example of this situation is a site header that is dynamic, but is only updated once or twice a day. Identifying content that can be moved off of Tomcat and onto a web server such as Apache will help unload your Tomcat servers to do what they do best - serve dynamic content.
While we're talking about auditing your content structure, now may be a good time to make a note to yourself to look into an open-source Java template solution such as Velocity or Freemarker in the near future.
JSP is a powerful, widely used technology, that affords full access to Java and Servlet API - more access than Velocity provides. Migration from JSP can be somewhat painful, especially if your architecture is dug in, and if it's working for you, there's no need to do it right away. Thanks to improvements such as tag libraries, JSP is becoming more powerful all the time.
However, as the Velocity and Freemarker projects mature, they are becoming more and more popular, and well-executed implementations can offer large performance boosts over JSP for specific use cases.
As mentioned earlier, your server will take the biggest performance hit from JSP pages at compilation. In order to mitigate this, there are a number of methods you can use to pre-compile the JSP code, instead of allowing it to compile under real-time load. Here's some information about the three most common methods of pre-compilation to help you choose the best solution for your site's needs.
Pre-Compilation By Request
We'll start with the most obvious of the techniques - simply requesting the JSP via a web client. Since JSPs are only compiled at first request, making this request yourself will ensure that the first actual user to visit the site will not have to wait for the JSP to compile.
If you only have a small number of JSPs, and you don't frequently restart your server, making a small script to automatically crawl all your JSP pages when your server launches may be all you need to give your performance a boost.
Is this feasible for production environments? No. But it can be very useful during development, as you will be able to see exactly what a user would see upon visiting your JSP for the first time, and correct any errors.
Pre-Compilation on Startup
The Java servlet specification Tomcat implements includes a number of JSP-specific functionalities, including syntax elements that designate JSPs to be compiled upon the launch of a web application. These elements should be configured on a per-application basis in "WEB-INF/web.xml". Here's an example configuration:
<servlet-name> YourJSP.jsp </servlet-name>
<jsp-file> /path/to/yourjsp.jsp </servlet-name>
<load-on-startup> 1 </load-on-startup>
The integer is used to dictate compile order, so you can create a pre-compilation hierarchy. This technique will eliminate compilation delay for the first user visiting a given pre-compiled JSP page, at the expense of a slower web-app restart cycle, and still allows you to alter the JSP as needed.
Be careful when enabling this attribute that other mechanisms you have enabled, such as automatic reloading for hot deployment, don't cause a combined performance hit to your server.
Pre-Compilation at Build
The last technique we'll discuss is also provides the greatest overall boost to performance. Rather than compiling JSP code dynamically on the Tomcat server, it is possible to use JspC to pre-compile your JSP code when building your web application. This technique can net you a performance increase of up to 4% in certain circumstances. (For the curious, the boost can be attributed to a difference in the way Tomcat maps dynamically generated servlets versus pre-compiled servlets).
It's up to you whether that increase is attractive enough for your situation to rationalize changing your architecture to rely on pre-compilation. For users in low-resource situations, this can be a very attractive option. For others, the hassle is not worth the performance boost.
There are a number of ways to implement build-time JSP compilation. The traditional hack is to use a modified Ant build-file to call JspC and map the servlet to "WEB-INF/web.xml". Instructions and a sample build file for this method are available on the Apache project site.
Now that we've looked at some of the methods you can use to improve JSP performance via modifications to your Tomcat configuration, let's take a look at some best practices you should be following when actually writing JSP code. An in-depth discussion is outside the scope of this article, but this list should give you a helpful head start as you conduct your own research.
The first of these, as you might have already guessed, has to do with efficiently implementing JSP's native support for cached data into your code, in ways that effectively re-use data, or handle the data with the most efficient caching method.
The second, object control, refers to taking extra care to define things like session length/scope, thread pool configuration, and buffer size.
If your JSP page has already done the work of generating certain static or dynamic content, don't make it do the work again. It's even possible to cache data by session or by application, which can be used to make non-secure dynamic content re-usable across all active sessions.
For static data generation, this means using the _jspInit() method to generate the content only once, and then calling it back up using _jspService(), rather than using out.print() every time the page is requested. Data generated with _jspInit() will last for the full lifetime of the servlet.
You can cache dynamic data using one of the 5 state persistence mechanisms available in JSP - Tomcat's native session persistence mechanism, cookies, URL rewriting, hidden fields, or a JDBC-based session persistence mechanism of your own design. Of these, Tomcat's native session support is easily the highest performer (although it has trouble scaling), followed by hidden fields.
A good solution to balance the load between client and server for scalable, secure caching is to use Tomcat's session persistence support to store secure data, and handle everything else with hidden fields.
An easy way to waste your server's CPU cycles is to get sloppy about closing sessions, re-using tags, and properly configuring buffers. This is a much broader topic than data caching, but here are some general areas to consider when cleaning up your JSP code.
Move Large Data Segments Efficiently
Moving large chunks of data all at once (images, for example) can hold up the entire page generation process. You can use the flush() method to force JSPWriter to move on, rather than stalling. Simply break the data-heavy code into chunks, with out.flush() in between. You should also consider setting the buffer size in your page directive to a higher value.
Closing Sessions and Objects
JSP includes mechanisms for handling unclosed sessions, orphan objects, and memory hungry processes, such as timeout and serialization. However, it is in the best interest of your server's performance that these methods are never used. It's much more efficient to write code that explicitly controls the way these objects are handled:
- Use jspDestroy() to remove unused connections and sockets
- Tune session timeout to your application's needs, and provide means of explicitly closing a session using the session.invalidate() method
- Add an entry to your application's context.xml file in Tomcat to disable serialization
- Tune the JSP thread pool according to the actual load needs of your application
Using the Features You Need
Finally, there are a number of features available in JSP that are either enabled by default, but not necessarily useful for your situation, or available and tempting, but only suitable for specific use cases. These include auto-reload features, HTTPSessions, custom tags, and threadsafe code locking.
Before you use features like these, read up on their performance costs, and consider this information in relation to the needs of your application. If you're not going to be re-using custom tags, for example, enabling them will rob you of processor power, and you won't be reaping any benefits.