Java Servlet Programming, 2nd Edition > 19. Odds and Ends > 19.8 Performance Tuning |
19.8 Performance TuningPerformance tuning servlets requires a slightly different mindset than performance tuning normal Java applications or applets. The reason is that the JVM running the servlets is expected to simultaneously handle dozens, if not hundreds, of threads, each executing a servlet. These coexisting servlets have to share the resources of the JVM in a way that normal applications do not. The traditional performance-tuning tricks still apply, of course, but they have a different impact when used in a heavily multithreaded system. What follows are some of the tricks that have the largest special impact on servlet developers. 19.8.1 Go Forth, but Don't ProsperAvoid the unnecessary creation of objects. This has always been good advice creating unnecessary objects wastes memory and wastes a fair amount of time as the objects are created. With servlets, it's even better advice. Traditionally many JVMs have used a global object heap that must be locked for each new memory allocation. While any servlet is creating a new object or allocating additional memory, no other servlet can do so. 19.8.2 Don't Append by ConcatenationAvoid concatenating several strings together. Use the append( ) method of StringBuffer instead. This too has always been good advice, but with servlets it's particularly tempting to write code like this to prepare a string for later output: String output; output += "<TITLE>"; output += "Hello, " + user; output += "</TITLE>"; Although this code looks nice and neat, when it runs it executes as if written roughly as follows, with a new StringBuffer and new String created on each line: String output; output = new StringBuffer().append("<TITLE>").toString(); output = new StringBuffer(output).append("Hello, ").toString(); output = new StringBuffer(output).append(user).toString(); output = new StringBuffer(output).append("</TITLE>").toString(); When efficiency counts, rewrite the original code to look like the following, so just one StringBuffer and one String are created: StringBuffer buf = new StringBuffer(); buf.append("<TITLE>"); buf.append("Hello, ").append(user); buf.append("</TITLE); output = buf.toString(); Note that using an array of bytes is even more efficient. 19.8.3 Limit SynchronizationSynchronize whenever necessary, but no more. Every synchronized block in a servlet slows the servlet's response time. Because the same servlet instance may handle multiple concurrent requests, it must, of course, take care to protect its class and instance variables with synchronized blocks. All the time one request thread is in a servlet's synchronized block, however, no other thread can enter the block. Therefore, it's generally best to keep these blocks as small as possible. You should also take a look at the worst-case result of thread contention. If the worst case is bearable (as with the counter example from Chapter 3), you can consider removing synchronization blocks entirely. Also consider using the SingleThreadModel tag interface, where the server manages a pool of servlet instances to guarantee each instance is used at most by one thread at a time. Servlets that implement SingleThreadModel don't need to synchronize access to their instance variables. Finally, remember that java.util.Vector and java.util.Hashtable are always internally synchronized, while the equivalent java.util.ArrayList and java.util.HashMap, introduced in JDK 1.2, are not synchronized unless requested. So if your Vector or Hashtable doesn't need synchronization and you're running on JDK 1.2, try the unsynchronized ArrayList or HashMap instead. 19.8.4 Buffer Your Input and OutputBuffer your input and your output, all your storage files, any streams loaded from a database, and so on. This almost always improves performance, but the improvement can be especially profound with servlets. The reason is reading and writing one unit at a time can slow down the entire server due to the frequent context switches that have to be made. Fortunately, you generally don't need to buffer when writing to a servlet's PrintWriter or ServletOutputStream or when reading from a servlet's BufferedReader or ServletInputStream. Most server implementations already buffer these streams. 19.8.5 Try Using an OutputStreamFor web pages using the Latin-1 character encoding, it's technically possible to use either a PrintWriter or a ServletOutputStream to write to the client. Using a PrintWriter is the recommended approach because it supports internationalization, but on some servers using a ServletOutputStream provides a noticeable performance increase, and ServletOutputStream conveniently has a long list of print( ) and println( ) methods left over from Servlet API 1.0 when there was no PrintWriter option. Just be careful. Many servers are just the opposite and with them using a PrintWriter provides higher performance. Unless you're sure of your deployment platform and have run comparable time trials, stick with PrintWriter. 19.8.6 Use a Profiling ToolThere are a number of Java profiling tools available that can help identify bottlenecks in your code. After all, most performance problems in server-side Java are caused not by the language or JVM but rather by a handful of bottlenecks; the trick is locating those bottlenecks. These analysis tools run in the background, observing as your web server handles requests, reporting a detailed summary of where time was spent as well as how memory was allocated. Two popular tools are OptimizeIt! from Intuitive Systems (http://www.optimizeit.com) and JProbe from Sitraka, formerly the KL Group (http://www.sitraka.com/jprobe). Many JVMs also accept command-line flags (-prof under JDK 1.1 and -Xrunhprof under JDK 1.2) to report some basic profiling information. To run the server under load you can use a tool such as Apache JMeter (http://java.apache.org/jmeter).
|