About Me

My photo
"Enoughtheory.com" had its humble beginning in the year 2011 by ( Founder of Enoughtheory.com ) Mr Ravi Kant Soni , an Enterprise Java and Spring Framework Specialist, with a bachelor degree (B.E) in Information Science and Engineering from Reva Institute of Technology at Bangalore. He has been into the software development discipline for many years now. Ravi has worn many hats throughout his tenure, ranging from software development, designing multi-tenant applications, integration of new technology into an existing system, to his current love of writing a Spring Framework book. Currently, he is a lead engineer at HCL Technology. Ravi has focused on Web and Enterprise development using Spring Framework for most of his career and has been extensively involved in application design and implementation. He has developed applications for Core-Bank, HR and Payroll System, and e-Commerce systems using Spring Framework. Ravi Kant Soni is author of book "Learning Spring Application development" http://learningspringapplicationdevelopment.com

Tuesday, 17 July 2012

Java virtual machine performs thread synchronization

How the Java virtual machine performs thread synchronization

Understanding threads, locks, monitors and more in Java bytecode

Threads and shared data

One of the strengths of the Java programming language is its support for multithreading at the language level. Much of this support centers on coordinating access to data shared among multiple threads.
The JVM organizes the data of a running Java application into several runtime data areas: one or more Java stacks, a heap, and a method area.

Inside the Java virtual machine, each thread is awarded a Java stack, which contains data no other thread can access, including the local variables, parameters, and return values of each method the thread has invoked. The data on the stack is limited to primitive types and object references. In the JVM, it is not possible to place the image of an actual object on the stack. All objects reside on the heap.
There is only one heap inside the JVM, and all threads share it. The heap contains nothing but objects. There is no way to place a solitary primitive type or object reference on the heap -- these things must be part of an object. Arrays reside on the heap, including arrays of primitive types, but in Java, arrays are objects too.
Besides the Java stack and the heap, the other place data may reside in the JVM is the method area, which contains all the class (or static) variables used by the program. The method area is similar to the stack in that it contains only primitive types and object references. Unlike the stack, however, the class variables in the method area are shared by all threads.

Object and class locks

As described above, two memory areas in the Java virtual machine contain data shared by all threads. These are:
  • The heap, which contains all objects
  • The method area, which contains all class variables
If multiple threads need to use the same objects or class variables concurrently, their access to the data must be properly managed. Otherwise, the program will have unpredictable behavior.
To coordinate shared data access among multiple threads, the Java virtual machine associates a lock with each object and class. A lock is like a privilege that only one thread can "possess" at any one time. If a thread wants to lock a particular object or class, it asks the JVM. At some point after the thread asks the JVM for a lock -- maybe very soon, maybe later, possibly never -- the JVM gives the lock to the thread. When the thread no longer needs the lock, it returns it to the JVM. If another thread has requested the same lock, the JVM passes the lock to that thread.
Class locks are actually implemented as object locks. When the JVM loads a class file, it creates an instance of class java.lang.Class. When you lock a class, you are actually locking that class's Class object.
Threads need not obtain a lock to access instance or class variables. If a thread does obtain a lock, however, no other thread can access the locked data until the thread that owns the lock releases it.

Monitors

The JVM uses locks in conjunction with monitors. A monitor is basically a guardian in that it watches over a sequence of code, making sure only one thread at a time executes the code.
Each monitor is associated with an object reference. When a thread arrives at the first instruction in a block of code that is under the watchful eye of a monitor, the thread must obtain a lock on the referenced object. The thread is not allowed to execute the code until it obtains the lock. Once it has obtained the lock, the thread enters the block of protected code.
When the thread leaves the block, no matter how it leaves the block, it releases the lock on the associated object.

Multiple locks

A single thread is allowed to lock the same object multiple times. For each object, the JVM maintains a count of the number of times the object has been locked. An unlocked object has a count of zero. When a thread acquires the lock for the first time, the count is incremented to one. Each time the thread acquires a lock on the same object, a count is incremented. Each time the thread releases the lock, the count is decremented. When the count reaches zero, the lock is released and made available to other threads.

Synchronized blocks

In Java language terminology, the coordination of multiple threads that must access shared data is called synchronization. The language provides two built-in ways to synchronize access to data: with synchronized statements or synchronized methods.

Synchronized statements

To create a synchronized statement, you use the synchronized keyword with an expression that evaluates to an object reference, as in the reverseOrder() method below:

class KitchenSync {
    private int[] intArray = new int[10];
    void reverseOrder() {
        synchronized (this) {
            int halfWay = intArray.length / 2;
            for (int i = 0; i < halfWay; ++i) {
                int upperIndex = intArray.length - 1 - i;
                int save = intArray[upperIndex];
                intArray[upperIndex] = intArray[i];
                intArray[i] = save;
            }
        }
    }
}
In the case above, the statements contained within the synchronized block will not be executed until a lock is acquired on the current object (this). If instead of a this reference, the expression yielded a reference to another object, the lock associated with that object would be acquired before the thread continued.
Two opcodes, monitorenter and monitorexit, are used for synchronization blocks within methods, as shown in the table below.

Table 1. Monitors


Opcode

Operand(s)

Description

monitorenter

none
pop objectref, acquire the lock associated with objectref

monitorexit

none
pop objectref, release the lock associated with objectref
When monitorenter is encountered by the Java virtual machine, it acquires the lock for the object referred to by objectref on the stack. If the thread already owns the lock for that object, a count is incremented. Each time monitorexit is executed for the thread on the object, the count is decremented. When the count reaches zero, the monitor is released.
Take a look at the bytecode sequence generated by the reverseOrder() method of the KitchenSync class.
Note that a catch clause ensures the locked object will be unlocked even if an exception is thrown from within the synchronized block. No matter how the synchronized block is exited, the object lock acquired when the thread entered the block definitely will be released.

Synchronized methods

To synchronize an entire method, you just include the synchronized keyword as one of the method qualifiers, as in:
class HeatSync {
    private int[] intArray = new int[10];
    synchronized void reverseOrder() {
        int halfWay = intArray.length / 2;
        for (int i = 0; i < halfWay; ++i) {
            int upperIndex = intArray.length - 1 - i;
            int save = intArray[upperIndex];
            intArray[upperIndex] = intArray[i];
            intArray[i] = save;
        }
    }
}
The JVM does not use any special opcodes to invoke or return from synchronized methods. When the JVM resolves the symbolic reference to a method, it determines whether the method is synchronized. If it is, the JVM acquires a lock before invoking the method. For an instance method, the JVM acquires the lock associated with the object upon which the method is being invoked. For a class method, it acquires the lock associated with the class to which the method belongs. After a synchronized method completes, whether it completes by returning or by throwing an exception, the lock is released.


Object Finalization and Cleanup

Object finalization and cleanup

How to design classes for proper object cleanup

  In this Design Techniques article, I'll be focusing on the design principles that help you ensure proper cleanup at the end of an object's life.

Why clean up?

Every object in a Java program uses computing resources that are finite. Most obviously, all objects use some memory to store their images on the heap. (This is true even for objects that declare no instance variables. Each object image must include some kind of pointer to class data, and can include other implementation-dependent information as well.) But objects may also use other finite resources besides memory. For example, some objects may use resources such as file handles, graphics contexts, sockets, and so on. When you design an object, you must make sure it eventually releases any finite resources it uses so the system won't run out of those resources.
Because Java is a garbage-collected language, releasing the memory associated with an object is easy. All you need to do is let go of all references to the object. Because you don't have to worry about explicitly freeing an object, as you must in languages such as C or C++, you needn't worry about corrupting memory by accidentally freeing the same object twice. You do, however, need to make sure you actually release all references to the object. If you don't, you can end up with a memory leak, just like the memory leaks you get in a C++ program when you forget to explicitly free objects. Nevertheless, so long as you release all references to an object, you needn't worry about explicitly "freeing" that memory.
Similarly, you needn't worry about explicitly freeing any constituent objects referenced by the instance variables of an object you no longer need. Releasing all references to the unneeded object will in effect invalidate any constituent object references contained in that object's instance variables. If the now-invalidated references were the only remaining references to those constituent objects, the constituent objects will also be available for garbage collection. Piece of cake, right?

The rules of garbage collection

Although garbage collection does indeed make memory management in Java a lot easier than it is in C or C++, you aren't able to completely forget about memory when you program in Java. To know when you may need to think about memory management in Java, you need to know a bit about the way garbage collection is treated in the Java specifications.

Garbage collection is not mandated
The first thing to know is that no matter how diligently you search through the Java Virtual Machine Specification (JVM Spec), you won't be able to find any sentence that commands, Every JVM must have a garbage collector. The Java Virtual Machine Specification gives VM designers a great deal of leeway in deciding how their implementations will manage memory, including deciding whether or not to even use garbage collection at all. Thus, it is possible that some JVMs (such as a bare-bones smart card JVM) may require that programs executed in each session "fit" in the available memory.
Of course, you can always run out of memory, even on a virtual memory system. The JVM Spec does not state how much memory will be available to a JVM. It just states that whenever a JVM does run out of memory, it should throw an OutOfMemoryError.
Nevertheless, to give Java applications the best chance of executing without running out of memory, most JVMs will use a garbage collector. The garbage collector reclaims the memory occupied by unreferenced objects on the heap, so that memory can be used again by new objects, and usually de-fragments the heap as the program runs.

Garbage collection algorithm is not defined
Another command you won't find in the JVM specification is All JVMs that use garbage collection must use the XXX algorithm. The designers of each JVM get to decide how garbage collection will work in their implementations. Garbage collection algorithm is one area in which JVM vendors can strive to make their implementation better than the competition's. This is significant for you as a Java programmer for the following reason:
Because you don't generally know how garbage collection will be performed inside a JVM, you don't know when any particular object will be garbage collected.
So what? you might ask. The reason you might care when an object is garbage collected has to do with finalizers. (A finalizer is defined as a regular Java instance method named finalize() that returns void and takes no arguments.) The Java specifications make the following promise about finalizers:
Before reclaiming the memory occupied by an object that has a finalizer, the garbage collector will invoke that object's finalizer.
Given that you don't know when objects will be garbage collected, but you do know that finalizable objects will be finalized as they are garbage collected, you can make the following grand deduction:
You don't know when objects will be finalized.
You should imprint this important fact on your brain and forever allow it to inform your Java object designs.

Finalizers to avoid

The central rule of thumb concerning finalizers is this:
Don't design your Java programs such that correctness depends upon "timely" finalization.
In other words, don't write programs that will break if certain objects aren't finalized by certain points in the life of the program's execution. If you write such a program, it may work on some implementations of the JVM but fail on others.

Don't rely on finalizers to release non-memory resources
An example of an object that breaks this rule is one that opens a file in its constructor and closes the file in its finalize() method. Although this design seems neat, tidy, and symmetrical, it potentially creates an insidious bug. A Java program generally will have only a finite number of file handles at its disposal. When all those handles are in use, the program won't be able to open any more files.
A Java program that makes use of such an object (one that opens a file in its constructor and closes it in its finalizer) may work fine on some JVM implementations. On such implementations, finalization would occur often enough to keep a sufficient number of file handles available at all times. But the same program may fail on a different JVM whose garbage collector doesn't finalize often enough to keep the program from running out of file handles. Or, what's even more insidious, the program may work on all JVM implementations now but fail in a mission-critical situation a few years (and release cycles) down the road.

Other finalizer rules of thumb
Two other decisions left to JVM designers are selecting the thread (or threads) that will execute the finalizers and the order in which finalizers will be run. Finalizers may be run in any order -- sequentially by a single thread or concurrently by multiple threads. If your program somehow depends for correctness on finalizers being run in a particular order, or by a particular thread, it may work on some JVM implementations but fail on others.
You should also keep in mind that Java considers an object to be finalized whether the finalize() method returns normally or completes abruptly by throwing an exception. Garbage collectors ignore any exceptions thrown by finalizers and in no way notify the rest of the application that an exception was thrown. If you need to ensure that a particular finalizer fully accomplishes a certain mission, you must write that finalizer so that it handles any exceptions that may arise before the finalizer completes its mission.
One more rule of thumb about finalizers concerns objects left on the heap at the end of the application's lifetime. By default, the garbage collector will not execute the finalizers of any objects left on the heap when the application exits. To change this default, you must invoke the runFinalizersOnExit() method of class Runtime or System, passing true as the single parameter. If your program contains objects whose finalizers must absolutely be invoked before the program exits, be sure to invoke runFinalizersOnExit() somewhere in your program.

So what are finalizers good for?

By now you may be getting the feeling that you don't have much use for finalizers. While it is likely that most of the classes you design won't include a finalizer, there are some reasons to use finalizers.
One reasonable, though rare, application for a finalizer is to free memory allocated by native methods. If an object invokes a native method that allocates memory (perhaps a C function that calls malloc()), that object's finalizer could invoke a native method that frees that memory (calls free()). In this situation, you would be using the finalizer to free up memory allocated on behalf of an object -- memory that will not be automatically reclaimed by the garbage collector.
Another, more common, use of finalizers is to provide a fallback mechanism for releasing non-memory finite resources such as file handles or sockets. As mentioned previously, you shouldn't rely on finalizers for releasing finite non-memory resources. Instead, you should provide a method that will release the resource. But you may also wish to include a finalizer that checks to make sure the resource has already been released, and if it hasn't, that goes ahead and releases it. Such a finalizer guards against (and hopefully will not encourage) sloppy use of your class. If a client programmer forgets to invoke the method you provided to release the resource, the finalizer will release the resource if the object is ever garbage collected. The finalize() method of the LogFileManager class, shown later in this article, is an example of this kind of finalizer.

Avoid finalizer abuse

The existence of finalization produces some interesting complications for JVMs and some interesting possibilities for Java programmers. What finalization grants to programmers is power over the life and death of objects. In short, it is possible and completely legal in Java to resurrect objects in finalizers -- to bring them back to life by making them referenced again. (One way a finalizer could accomplish this is by adding a reference to the object being finalized to a static linked list that is still "live.") Although such power may be tempting to exercise because it makes you feel important, the rule of thumb is to resist the temptation to use this power. In general, resurrecting objects in finalizers constitutes finalizer abuse.
The main justification for this rule is that any program that uses resurrection can be redesigned into an easier-to-understand program that doesn't use resurrection. A formal proof of this theorem is left as an exercise to the reader (I've always wanted to say that), but in an informal spirit, consider that object resurrection will be as random and unpredictable as object finalization. As such, a design that uses resurrection will be difficult to figure out by the next maintenance programmer who happens along -- who may not fully understand the idiosyncrasies of garbage collection in Java.
If you feel you simply must bring an object back to life, consider cloning a new copy of the object instead of resurrecting the same old object. The reasoning behind this piece of advice is that garbage collectors in the JVM invoke the finalize() method of an object only once. If that object is resurrected and becomes available for garbage collection a second time, the object's finalize() method will not be invoked again.

Managing non-memory resources

Because heap memory is automatically reclaimed by the garbage collector, the main thing you need to worry about when you design an object's end-of-lifetime behavior is to ensure that finite non-memory resources, such as file handles or sockets, are released. You can take any of three basic approaches when you design an object that needs to use a finite non-memory resource:
  1. Obtain and release the resource within each method that needs the resource
  2. Provide a method that obtains the resource and another that releases it
  3. Obtain the resource at creation time and provide a method that releases it

Approach 1: Obtain and release within each relevant method

As a general rule, the releasing of non-memory finite resources should be done as soon as possible after their use because the resources are, by definition, finite. If possible, you should try to obtain a resource, use it, then release it all within the method that needs the resource.
A log file class: An example of Approach 1
An example of a class where Approach 1 might make sense is a log file class. Such a class takes care of formatting and writing log messages to a file. The name of the log file is passed to the object as it is instantiated. To write a message to the log file, a client invokes a method in the log file class, passing the message as a String. Here's an example:
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.IOException;
class LogFile {
    private String fileName;
    LogFile(String fileName) {
        this.fileName = fileName;
    }
    // The writeToFile() method will catch any IOException
    // so that clients aren't forced to catch IOException
    // everywhere they write to the log file.  For now,
    // just fail silently. In the future, could put
    // up an informative non-modal dialog box that indicates
    // a logging error occurred. - bv 4/15/98
    void writeToFile(String message) {
        FileOutputStream fos = null;
        PrintWriter pw = null;
        try {
            fos = new FileOutputStream(fileName, true);
            try {
                pw = new PrintWriter(fos, false);
                pw.println("------------------");
                pw.println(message);
                pw.println();
            }
            finally {
                if (pw != null) {
                    pw.close();
                }
            }
        }
        catch (IOException e) {
        }
        finally {
            if (fos != null) {
                try {
                    fos.close();
                }
                catch (IOException e) {
                }
            }
        }
    }
}


Class LogFile is a simple example of Approach 1. A more production-ready LogFile class might do things such as:
  • Insert the date and time each log message was written
  • Allow messages to be assigned a level of importance (such as ERROR, INFO, or DEBUG) and enable a level to be set that will prevent unwanted detail (such as DEBUG messages) from making it into the log file
  • Manage in some way the size of the log file, i.e., by copying it to a different filename and starting fresh each time the log file achieves a certain size


The main feature of this simple version of class LogFile is that it surrounds each log message with a series of dashes and a blank line.

Using finally to ensure resource release
Note that in the writeToFile() method, the releasing of the resource is done in finally clauses. This is to make sure the finite resource (file handle) is actually released no matter how the code is exited. If an IOException is thrown, the file will be closed.

Pros and cons of Approach 1
The approach to resource management taken by class LogFile (Approach 1 from the above list) helps make your class easy to use, because client programmers don't have to worry about explicitly obtaining or releasing the resource. In both Approach 2 and 3 from the list above client programmers must remember to explicitly invoke a method to release the resource. In addition -- and what can be far more difficult -- client programmers must figure out when their programs no longer need a resource.
A problem with Approach 1 is that obtaining and releasing the resource each time you need it may be too inefficient. Another problem is that, in some situations, you may need to hold onto the resource between invocations of methods that use the resource (such as writeToFile()), so no other object can have access to it. In such cases, one of the other two approaches is preferable.

Approach 2: Offer methods for obtaining and releasing resources

In Approach 2 from the list above, you provide one method for obtaining the resource and another method for releasing it. This approach enables the same class instance to obtain and release a resource multiple times. Here's an example:

import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.IOException;
class LogFileManager {
    private FileOutputStream fos;
    private PrintWriter pw;
    private boolean logFileOpen = false;
    LogFileManager() {
    }
    LogFileManager(String fileName) throws IOException {
        openLogFile(fileName);
    }
    void openLogFile(String fileName) throws IOException {
        if (!logFileOpen) {
            try {
                fos = new FileOutputStream(fileName, true);
                pw = new PrintWriter(fos, false);
                logFileOpen = true;
            }
            catch (IOException e) {
                if (pw != null) {
                    pw.close();
                    pw = null;
                }
                if (fos != null) {
                    fos.close();
                    fos = null;
                }
                throw e;
            }
        }
    }
    void closeLogFile() throws IOException {
        if (logFileOpen) {
            pw.close();
            pw = null;
            fos.close();
            fos = null;
            logFileOpen = false;
        }
    }
    boolean isOpen() {
        return logFileOpen;
    }
    void writeToFile(String message) throws IOException {
        pw.println("------------------");
        pw.println(message);
        pw.println();
    }
    protected void finalize() throws Throwable {
        if (logFileOpen) {
            try {
                closeLogFile();
            }
            finally {
                super.finalize();
            }
        }
    }
}


In this example, class LogFileManager declares methods openLogFile() and closeLogFile(). Given this design, you could write to multiple log files with one instance of this class. This design also allows a client to monopolize the resource for as long as it wants. A client can write several consecutive messages to the log file without fear that another thread or process will slip in any intervening messages. Once a client successfully opens a log file with openLogFile(), that log file belongs exclusively to that client until the client invokes closeLogFile().
Note that LogFileManager uses a finalizer as a fallback in case a client forgets to invoke closeLogFile(). As mentioned earlier in this article, this is one of the more common uses of finalizers.
Note also that after invoking closeLogFile(), LogFileManager's finalizer invokes super.finalize(). Invoking superclass finalizers is good practice in any finalizer, even in cases (such as this) where no superclass exists other than Object. The JVM does not automatically invoke superclass finalizers, so you must do so explicitly. If someone ever inserts a class that declares a finalizer between LogFileManager and Object in the inheritance hierarchy, the new object's finalizer will already be invoked by LogFileManager's existing finalizer.
Making super.finalize() the last action of a finalizer ensures that subclasses will be finalized before superclasses. Although in most cases the placement of super.finalize() won't matter, in some rare cases, a subclass finalizer may require that its superclass be as yet unfinalized. So, as a general rule of thumb, place super.finalize() last.

Approach 3: Claim resource on creation, offer method for release

In the last approach, Approach 3 from the above list, the object obtains the resource upon creation and declares a method that releases the resource. Here's an example:
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.IOException;
class LogFileTransaction {
    private FileOutputStream fos;
    private PrintWriter pw;
    private boolean logFileOpen = false;
    LogFileTransaction(String fileName) throws IOException {
        try {
            fos = new FileOutputStream(fileName, true);
            pw = new PrintWriter(fos, false);
            logFileOpen = true;
        }
        catch (IOException e) {
            if (pw != null) {
                pw.close();
                pw = null;
            }
            if (fos != null) {
                fos.close();
                fos = null;
            }
            throw e;
        }
    }
    void closeLogFile() throws IOException {
        if (logFileOpen) {
            pw.close();
            pw = null;
            fos.close();
            fos = null;
            logFileOpen = false;
        }
    }
    boolean isOpen() {
        return logFileOpen;
    }
    void writeToFile(String message) throws IOException {
        pw.println("------------------");
        pw.println(message);
        pw.println();
    }
    protected void finalize() throws Throwable {
        if (logFileOpen) {
            try {
                closeLogFile();
            }
            finally {
                super.finalize();
            }
        }
    }
}


This class is called LogFileTransaction because every time a client wants to write a chunk of messages to the log file (and then let others use that log file), it must create a new LogFileTransaction. Thus, this class models one transaction between the client and the log file.
One interesting thing to note about Approach 3 is that this is the approach used by the FileOutputStream and PrintWriter classes used by all three example log file classes. In fact, if you look through the java.io package, you'll find that almost all of the java.io classes that deal with file handles use Approach 3. (The two exceptions are PipedReader and PipedWriter, which use Approach 2.)

Conclusion

The most important point to take away from this article is that if a Java object needs to take some action at the end of its life, no automatic way exists in Java that will guarantee that action is taken in a timely manner. You can't rely on finalizers to take the action, at least not in a timely way. You will need to provide a method that performs the action and encourage client programmers to invoke the method when the object is no longer needed.
This article contained several guidelines that pertain to finalizers:
  • Don't design your Java programs such that correctness depends on "timely" finalization
  • Don't assume that a finalizer will be run by any particular thread
  • Don't assume that finalizers will be run in any particular order
  • Avoid designs that require finalizers to resurrect objects; if you must use resurrection, prefer cloning over straight resurrection
  • Remember that exceptions thrown by finalizers are ignored
  • If your program includes objects with finalizers that absolutely must be run before the program exits, invoke runFinalizersOnExit(true) in class Runtime or System
  • Unless you are writing the finalizer for class Object, always invoke super.finalize() at the end of your finalizers
  •  
A request for reader participation

Software design is subjective. Your idea of a well-designed program may be your colleague's maintenance nightmare. In light of this fact, I am trying to make this column as interactive as possible.
I encourage your comments, criticisms, suggestions, flames -- all kinds of feedback -- about the material presented in this column. If you disagree with something, or have something to add, please let me know.

J2EE Application Performance Optimization

J2EE application performance optimization

How to extract maximum performance from your J2EE Web applications

 

In today's world of larger-then-ever software applications, users still expect real-time data and software that can process data at blazing speeds. With the advent of broadband access, users have grown accustomed to receiving responses in seconds, irrespective of how much data the server handles. It is becoming increasingly important for Web applications to provide as short response times as possible. The most obvious and simple way to improve a Website's performance is by scaling up hardware. But scaling the hardware does not work in all cases and is definitely not the most cost-effective approach. Other solutions can improve performance, without extra costs for the hardware. This article provides some suggestions that prove helpful when trying to maximize a J2EE Web application's performance.
If you decide to try this article's recommendations, keep in mind this article provides suggestions only. Performance tuning is as much an art as it is a science. Changes that often result in improvement might not make any difference in some cases, or, in rare scenarios, they can result in degradation. For best results with performance tuning, take a holistic approach.

Overview

Figure 1 illustrates at a broad level how a J2EE application appears when deployed in a production environment. To get the best performance from a J2EE application, all the underlying layers must be tuned, including the application itself.


Figure 1. J2EE application architecture. Click on thumbnail to view full-sized image.
For maximum performance, all the components in Figure 1—operating system, Web server, application server, and so on—need to be optimized. This article will give a glimpse into tuning a J2EE application server, Web server, relational database, and your J2EE application. For maximum payoff from this article, follow these guidelines:
  • Set a goal: Before you begin tuning your J2EE application's performance, set a goal. Often this goal addresses the maximum concurrent users the application will support for a given limit on response times. But the goal can also focus on other variables—for example, the response times should not increase more than 10 percent during the peak hour of user load.
  • Identify problem areas: It is important to identify the bottlenecks when you start making changes to improve performance. A little investigation into problems might reveal the specific component that causes poor performance. For example, if the CPU usage on an application server is high, you will want to focus on tuning the application server first.
  • Follow a methodical and focused path: Once the goal is set, try to make changes that are expected to have the biggest impact on performance. Your time is better spent tuning a method that takes 10 seconds but gets called 100 times than tuning a method that takes one minute but gets called only once. In an ideal world, you test one change at a time before using it in a production environment. You make one change and stress-test it. If the change results in positive impact, only then will you make it permanent.

Identify bottlenecks

The goal of performance tuning is to identify bottlenecks and remove them. It is an iterative process. Once one area of the application improves, another area will become a bottleneck. You must repeat the cyclic process of first identifying the bottleneck, then resolving the bottleneck, then identifying the next bottleneck until the desired goal has been reached. You will need two kinds of tools that prove helpful in this process. First, you need stress tools that generate load for your application. Second, you need monitoring tools that collect data for various performance indicators.

Stress tools

Many different stress tools are available in the market today. Some of the popular ones are:
  • Mercury Interactive's LoadRunner
  • Segue's SilkPerformer
  • RadView Software's WebLoad
For a comprehensive list of stress tools see this article's Resources section. You must choose a tool that best fits your needs based on the tool's features and associated price tag. Some of the important features you should consider before choosing a tool are:
  • Support for a large number of concurrent Web users, each running through a predefined set of URL requests
  • Ability to record test scripts automatically from browser
  • Support for cookies, HTTP request parameters, HTTP authentication mechanisms, and HTTP over SSL (Secure Socket Layer)
  • Option to simulate requests from multiple client IP addresses
  • Flexible reporting module that lets you specify the log level and analyze the results long after the tests were run
  • Option to specify the test duration, schedule test for a later time, and specify the total load
For the sample tests mentioned in this article, LoadRunner was used.

Performance monitors

Using a monitoring tool, you collect data for various system performance indicators for all the appropriate nodes in your network topology. Many stress tools also provide monitoring tools. Windows OS also has a built-in performance monitor sufficient for many purposes. This Windows performance monitor can be started from the Administrative Tools menu, accessed from the Control Panel menu, or by typing "perfmon" in the Run window (accessed from the Start menu). You can display the performance counters data in real time, but usually, you'll want to log this data into a file so it can be viewed later. To log the data into a file, go to the Counter Logs selection in the left-hand side of the Performance window, right click with your mouse, and select New Log Settings as shown in Figure 2.


Figure 2. Windows performance monitor. Click on thumbnail to view full-sized image.
You can also set the file path/name for where the data should be logged, as well as a schedule of when to collect this data. It is possible to log the data and view it in real time, but the performance counters must be added twice—first, in the System Monitor link on the left-hand side and second, in Counter Logs, as shown above.
Many performance counters are available in Windows OS. The following table lists some of the important counters that you should always monitor:
System resource Performance monitor Description
CPU System: Processor queue length Processor queue length indicates the number of threads in the server's processor queue waiting to be executed by the CPU. As a rule of thumb, if the processor queue remains at a value higher than 2 for an extended period of time, most likely, the CPU is a bottleneck.

Processor: Percent of processor time This counter provides the total overall CPU utilization for the server. A value that exceeds 70-80 percent for long periods indicates a CPU bottleneck.

System: Percent of total privileged time This counter measures what percentage of the total CPU execution time is used for running in kernel/privileged mode. All the I/O operations run under kernel mode, so a high value (about 20-30 percent) usually indicates problems with the network, disk, or any other I/O interface.
RAM Memory: Pages per second Pages per second is the number of pages read from or written to the disk for resolving hard page faults. A value that continuously exceeds 25-30 indicates a memory bottleneck.

Memory: Available bytes Available bytes is the amount of physical memory available to processes running on the computer, in bytes. A low value (less than 10 MB) usually means the machine requires more RAM.
Hard disk Physical disk: Average disk queue length The average number of requests queued for the selected disk. A sustained value above 2 indicates an I/O bottleneck.

Physical disk: Percent of disk time The percentage of elapsed time that the selected disk drive is busy servicing requests. A continuous value above 50 percent indicates a bottleneck with hard disks.
Network Network interface: Total bytes per second Shows the bytes transfer rate (sent and received) on the selected network interface. Depending on the network interface bandwidth, this counter can tell if the network is the problem.

Network interface: Output queue length Indicates the length of an output packet queue in packets. A sustained value higher than 2 indicates a bottleneck in the network interface.


You should add the above counters (and any others as appropriate) in your counter log and collect this data while you stress-test your application using the stress tool. A file generated by the counter log can be opened later by clicking on the View Log File Data button on the right-hand side toolbar. Looking at these counters should give you some hint as to where the problem exists—application server, Web server, or database server.

After identifying the bottleneck this way, you should try to resolve it. You can use two different strategies—either tune the hosting environment in which your application runs or tune the application itself.

Environment tuning

In this section, we look at the possible tuning options in a typical J2EE Web application hosting environment. As already discussed above, a J2EE application environment usually consists of an application server, Web server, and a backend database.

Web server/application server tuning

Most application servers and Web servers provide similar kinds of configuration options though they have different mechanisms to set them. In this article, I cover Tomcat 4.1.x and Apache 1.3.27, which talk to each other using the JK connector. The configuration options presented here exist in most of the other application servers and Web servers, but you will need to locate the correct place to set them.
Apache
Probably the most important setting for your Windows Apache HTTP Server is the option for number of threads. This value should be high enough to handle the maximum number of concurrent users, but not so high that it starts adding its own overhead of too many context switches. The optimum value can be determined by monitoring the number of threads in use during peak hours. To monitor the threads in use, make sure you have the following configuration directives present in the Apache configuration file (httpd.conf):
LoadModule status_module modules/mod_status.so
<Location /server-status>
    SetHandler server-status
    Allow from all
</Location>


Now, from your browser, make an HTTP request to your Apache server with this URL: http://<apache_machine>/server-status. It displays how many requests are being processed and their status (reading request, writing response, etc.). Monitor this page during peak load on the server to ensure the server is not running out of idle threads. After you come up with the optimum number of threads for your application, change the ThreadsPerChild directive in the configuration file to an appropriate value.
A few other items that improve performance in the Apache HTTP Server are:
  • DNS reverse lookups are inefficient. Switch off DNS lookups by setting HostnameLookups in the configuration file to off.
  • Do not load unnecessary modules. Apache allows dynamic modules that extend basic functionality of the Apache HTTP Server. Comment out all the LoadModule directives you don't need.
  • Try to minimize logging as much as possible. Look for directives LogLevel, CustomLog, and LogFormat in the configuration file for changing logging level.
  • Minimize the JK connector's logging also by setting the JkLogLevel directive to emerg.
Tomcat

The two most important configuration options for Tomcat are its heap size and number of threads. Unfortunately there is no good way to determine the heap size needed because in most cases, the JVM doesn't start cleaning up the memory until it reaches the maximum memory allocated. One good rule of thumb is to allocate half of the total physical RAM as Tomcat heap size. If you still run out of memory, look into your application design to reduce memory usage, identify any memory leaks, or try various garbage collector options in the JVM. To change the heap size add -Xms<size> -Xmx<size> as the JVM parameter in the command line that starts Tomcat. <size> is the JVM heap size usually specified in megabytes by appending a suffix m, for example, 512m. Initial heap size is -Xms, and -Xmx is the maximum heap size. For server applications, both should be set to the same value.

The number of threads in Tomcat can be modified by changing the values of minProcessors and maxProcessors attributes for the appropriate connector in <Tomcat>/conf/server.xml. If you are using the JK connector, change the values of its attributes. Again, there is no simple way to decide the optimum value for these attributes. The value should be set such that enough threads are available to handle your Web application's peak load. You can monitor a process's current thread count in the Windows Task Manager, which can assist in determining the correct value of these attributes.
A few other options you should be aware of:
  • If you are not using the latest JRE (Java Runtime Environment), consider upgrading to the latest one. I have seen up to a 30 percent performance improvement after upgrading from JRE 1.3.1 to JRE 1.4.1.
  • Add the -server option to the JVM options for Tomcat, which should result in better performance for server applications. Note that this option, in some cases, causes the JVM to crash for no apparent reason. If you face this problem, remove the option.
  • Change the default Jasper (JavaServer Pages, or JSP, compiler) settings in <Tomcat>/conf/web.xml by setting development="false", reloading="false" and logVerbosityLevel="FATAL".
  • Minimize logging in Tomcat by setting debug="0" everywhere in <Tomcat>/conf/server.xml.
  • Remove any unnecessary resources from the Tomcat configuration file. Some examples include the Tomcat examples Web application and extra <Connector>, <Listener> elements.
  • Set the autodeploy attribute of the <Host> tag to false (unless you need any of the default Tomcat applications like Tomcat Manager).
  • Make sure you have set reloadable="false" for all your Web application contexts in <Tomcat>/conf/server.xml.

Database tuning

In the case of Microsoft SQL Server, more often than not, you do not need to modify any configuration options, since it automatically tunes your database to a great degree. You should change these settings only if your stress tests identify the database as a bottleneck. Some of the configuration options that you can try are:
  • Run your SQL Server on a dedicated server instead of a shared machine.
  • Keep your application database and your temporary database on different hard disks.
  • Consider taking local backups and moving them to a different machine. The backups should complete much faster.
  • Normalize your database to the third normal form. This is usually the best compromise, as the fourth and fifth forms of normalization can result in performance degradation.
  • If you have more than 4 GB of physical RAM available, set the awe enabled configuration option to 1, which will allow SQL Server to use more than 4 GB of memory up to a maximum of 64 GB (depending on the SQL Server edition).
  • In case you have many concurrent queries executing and enough memory is available, you can increase the value of the min memory per query option (default is 1,024 KB).
  • Change the value of the max worker threads option, which indicates the maximum number of user connections allowed. Once this limit is reached, any new user requests will wait until one of the existing worker threads finishes its current task. The default value for this option is 255.
  • Set the priority boost option to 1. This will allow SQL Server to run with a higher priority as compared to the other applications running on the same server. If you are running on a dedicated server, it is usually safe to set this option.
If none of the configuration options resolve the bottleneck, you should consider scaling up the database server. Horizontal scaling is not possible in SQL Server, as it does not support true clustering, so usually, the only option is vertical scaling.

Application tuning

After you have tuned your hosting environment, now it is time to get down and dirty inside your application source code and database schema. In this section, we look at one of the many possible ways to tune your Java code and your SQL queries.

Java code optimization

The most popular way to optimize Java code is by using a profiler. Sun's JVM has built-in support for profiling (Java Virtual Machine Profiler Interface, or JVMPI) that can be switched at execution time by passing the right JVM parameters. Many commercial profilers are available; some rely on JVMPI, others provide their own custom hooks into Java applications (using bytecode instrumentation or some other method). But be aware that all these profilers add significant overhead. Thus, your application cannot be profiled at a realistic load level. Use these profilers with a single user or a limited number of users. It is still a good idea to run your application through the profiler and analyze the results for any obvious bottlenecks.
To identify your application's slowest areas in a full-fledged deployed environment, you can add your own timing logs to the application, which can be switched off easily in the production environment. A logging API, such as log4j or J2SE 1.4's Java Logging API, is handy for this purpose. The code below shows a sample utility class that can help you add timing logs to your application:
import java.util.HashMap;
//Import org.apache.log4j.Logger;
public class LogTimeStamp {
    private static HashMap ht = new HashMap();
    // Preferably we should use log4j instead of System.out
//    private static Logger logger = Logger.getLogger("LogTimeStamp");
    private static class ThreadExecInfo {
        long timestamp;
        int stepno;
    }
    public static void LogMsg(String Msg) {
        LogMsg(Msg, false);
    }
    /*
     * Passing true in the second parameter of this function resets the counter for
     * the current thread. Otherwise it keeps track of the last invocation and prints
     * the current counter value and the time difference between the two invocations.
     */
    public static void LogMsg(String Msg, boolean flag) {
        LogTimeStamp.ThreadExecInfo thr;
        long timestamp = System.currentTimeMillis();
        synchronized (ht) {
            thr = (LogTimeStamp.ThreadExecInfo) ht.get(Thread.currentThread().getName());
            if (thr == null) {
                thr = new LogTimeStamp.ThreadExecInfo();
                ht.put(Thread.currentThread().getName(), thr);
            } 
        }
        if (flag == true) {
            thr.stepno = 0;
        }
        if (thr.stepno != 0) {
//            logger.debug(Thread.currentThread().getName() + ":" + thr.stepno + ":" +
//                    Msg + ":" + (timestamp - thr.timestamp));
            System.out.println(Thread.currentThread().getName() + ":" + thr.stepno + ":" +
                    Msg + ":" + (timestamp - thr.timestamp));
        }
        thr.stepno = thr.stepno + 1;
        thr.timestamp = timestamp;
    }
}


After adding the above class in your application, you must invoke method LogTimeStamp.LogMsg() at various checkpoints in your code. This method prints the time (in milliseconds) it took for one thread to get from one checkpoint to the next one. First, call LogTimeStamp.LogMsg("Your Msg", true) at one place in the code that is the start of a user request. Now you can insert the following invocations in your code:


    public void startingMethod() {
        ...
        LogTimeStamp.LogMsg("This is a test message", true); //This is starting point
        ...
        LogTimeStamp.LogMsg("One more test message"); //This will become check point 1
        method1();
        ...
    }
    public void method1() {
        ...
        LogTimeStamp.LogMsg("Yet another test message"); //This will become check point 2
        method2();
        ...
        LogTimeStamp.LogMsg("Oh no another test message"); //This will become check point 4
    }
    public void method2() {
        ...
        LogTimeStamp.LogMsg("Wow! another test message"); //This will become check point 3
        ...
    }


The Perl script analyze.pl, which can be downloaded from Resources, can take the output of the above log messages as input and print the results in the format below. From these results, you now know which part of the code requires the most time and can concentrate on optimizing that part:
Transactions                           Avg. Time  Max Time  Min Time
--------------------------------------------------------------------
[This is a ...] to [One more t...]         14410     20937      7500
[One more t...] to [Yet anothe...]            16        62         0
[Yet anothe...] to [Wow! anoth...]         39860     50844     27703
[Wow! anoth...] to [Oh no anot...]           711      1844        94
[Oh no anot...] to [OK thats e...]         68089    228452     19718


The above approach represents just one of the ways to tune your Java code. You can use whatever methodology works for you. Some excellent resources are available for Java performance tuning. Check Resources for some links. Some general suggestions you should be aware of while developing a J2EE application are:
  • Avoid using synchronized blocks in your code as much as possible. That does not mean you should abdicate handling synchronization for your code's multithreaded parts, but you should try to limit its usage. Synchronized blocks can severely impair your application's scalability.
  • Proper logging proves necessary in serious software development. You should try to use a logging mechanism (like log4j) that lets you switch off logging in the production environment to reduce logging overhead.
  • Instead of creating and destroying resources every time you need them, use a resource pool for every resource that is costly to create. One obvious choice for this is your JDBC (Java Database Connectivity) Connection objects. Threads are also usually good candidates for pooling. Many free APIs are available for pooling various resources.
  • Try to minimize the objects you store in HttpSession. Extra objects in HttpSession not only lead to more memory usage, they also add additional overhead for serialization/deserialization in case of persistent sessions.
  • Where ever possible, use RequestDispatcher.forward() instead of HttpServletResponse.sendRedirect(), as the latter involves a trip to the browser.
  • Minimize the use of SingleThreadModel in servlets so that the servlet container does not have to create many instances of your servlet.
  • Java stream objects perform better than reader/writer objects because they do not have to deal with string conversion to bytes. Use OutputStream in place of PrintWriter.
  • Reduce the default session timeout either by changing your servlet container configuration or by calling HttpSession.setMaxInactiveInterval() in your code.
  • Just as we switched off the DNS lookup in the Web server configuration, try not to use ServletRequest.getRemoteHost(), which involves a reverse DNS lookup.
  • Always add directive <%@ page session="false"%> to JSP pages where you do not need a session.
  • Excessive use of custom tags also may result in poor performance. Keep performance in mind while designing your custom tag library.

SQL query optimization

The optimization of SQL queries is a vast subject in itself, and many books cover only this topic. SQL query running times can vary by many orders of magnitude even if they return the same results in all cases. Here I just show how to identify the slow queries and offer a few suggestions as to how to fix some of the most common mistakes.

First of all, to identify slow queries, you can use SQL Profiler, a tool from Microsoft that comes standard with SQL Server 2000. This tool should be run on a machine other than your SQL Server database server and the results should be stored in a different database as well. Storing results in a database allows all kinds of reports to be generated using standard SQL queries. Profiling any application inherently adds a lot of overhead, so try to use appropriate filters that can reduce the total amount of data collected.
To start the profiling, from SQL Profiler's File menu, select New, then Trace, and give the connection information and the appropriate credentials to connect to the database you want to profile. A Trace Properties windows will open, where you should enter a meaningful name you will recognize later. Select Save To Table option and now give the connection information and credentials for the database server (this should differ from the server you are profiling) where you want to store the data collected by the profiler. Next, you should be asked to provide the database and the table name where the results will be stored. Usually, you would also want to add a filter, so go to the Filters tab and add the appropriate filters (for example "duration greater than or equal to 500 milliseconds" or "CPU greater than or equal to 20" as shown in Figure 3). Now click on the Run button and the profiling will start.


Figure 3. SQL Profiler. Click on thumbnail to view full-sized image.
Let's say based on SQL Profile's results, you have identified the following query as the most time consuming:
SELECT [TABLE1].[T1COL1], [TABLE1].[T1COL2], 
       [TABLE1].[T1COL3], [TABLE1].[T1COL4]
FROM ((([TABLE1] LEFT JOIN [TABLE4] ON [TABLE1].[T1COL4] = [TABLE4].[T4COL4]) 
        LEFT JOIN [TABLE3] ON [TABLE1].[T1COL3] = [TABLE3].[T3COL3]) 
        LEFT JOIN [TABLE2] ON [TABLE1].[T1COL2] = [TABLE2].[T2COL2]) 
WHERE [TABLE1].[T1COL5] = 'VALUE1'


Now your task is to optimize this query to improve performance. You look at your database and find that TABLE1 has 700,000 records, TABLE2 has 16, TABLE3 has 100, and TABLE4 happens to have more than 4 million records. You should first understand this query's cost, and Query Analyzer comes in handy for this task. Select Show Execution Plan in the Query menu and execute this query in Query Analyzer. Figure 4 shows the resulting execution plan.


Figure 4. Execution plan before indexes. Click on thumbnail to view full-sized image.
From this plan, you see that SQL Server is clearly doing full-table scans for all four tables, and together, they make up around 80 percent of the total query cost. Luckily, another feature in Query Analyzer can analyze a query and recommend appropriate indexes. Run Index Tuning Wizard from the Query menu again. This wizard analyzes the query and gives recommendations for indexes. As shown in Figure 5, it recommends two indexes to be created ([TABLE4].[T4COL4] & [TABLE1].[T1COL5]) and also indicates performance will improve 99 percent!


Figure 5. Index Tuning Wizard. Click on thumbnail to view full-sized image.
After creating the indexes, the execution time declines from 4,125 milliseconds to 110 milliseconds, and the new execution plan shown in Figure 6 shows only two table scans (not a problem as TABLE2 and TABLE3 both have limited records).


Figure 6. Execution plan after indexes. Click on thumbnail to view full-sized image.
This was just an example of what proper tuning can achieve in terms of performance. In general, SQL Server's auto-tuning features automatically handle many tasks for you. For example, reordering WHERE clauses will never yield any benefit, as SQL Server internally handles that. Still, here are a few things you should keep in mind while writing SQL queries:
  • Keep the transactions as short as possible. The longer a transaction is open, the longer it holds the locks on the resources acquired, and every other transaction must wait.
  • Do not use DISTINCT clauses unnecessarily. If you know the rows will be unique, do not add DISTINCT in the SELECT clause.
  • When possible, avoid using SELECT *. Select only the columns from which you need the data.
  • Consider adding indexes to those columns causing full-table scans for your queries. Indexes can result in a big performance gain, as shown above, even though they consume extra disk space.
  • Avoid using too many string functions or operators in your queries. Functions like SUBSTRING, LOWER, UPPER, and LIKE result in poor performance.

Summary

In this article, I barely scratched the surface of performance tuning. Just reading this article will not make you an expert on all aspects of performance. But I hope you have gained a better appreciation for the methodology and acquired enough knowledge to be able to start the process. For optimum results, you need to tune every aspect of the application starting with the bottlenecks. Even experienced developers make small mistakes that prove costly in terms of performance. Performance tuning helps to identify these mistakes and makes your product not only more scalable, but also more reliable under stress conditions.







Improve Your Web Page Performance

Ways to Improve Your Web Page Performance

(front-end performance)

There are a million and one ways to boost your website’s performance. The methods vary and some are more involved than others. The three main areas that you can work on are: hardware (your web server), server-side scripting optimization (PHP, Python, Java), and front-end performance (the meat of the web page).
This article primarily focuses on front-end performance since it’s the easiest to work on and provides you the most bang for your buck.

Why focus on front-end performance?

The front-end (i.e. your HTML, CSS, JavaScript, and images) is the most accessible part of your website. If you’re on a shared web hosting plan, you might not have root (or root-like) access to the server and therefore can’t tweak and adjust server settings. And even if you do have the right permissions, web server and database engineering require specialized knowledge to give you any immediate benefits.
It’s also cheap. Most of the front-end optimization discussed can be done at no other cost but your time. Not only is it inexpensive, but it’s the best use of your time because front-end performance is responsible for a very large part of a website’s response time.
With this in mind, here are a few simple ways to improve the speed of your website.

1. Profile your web pages to find the culprits.

Firebug screen shot.

It’s helpful to profile your web page to find components that you don’t need or components that can be optimized. Profiling a web page usually involves a tool such as Firebug to determine what components (i.e. images, CSS files, HTML documents, and JavaScript files) are being requested by the user, how long the component takes to load, and how big it is. A general rule of thumb is that you should keep your page components as small as possible (under 25KB is a good target).
Firebug’s Net tab (shown above) can help you hunt down huge files that bog down your website. In the above example, you can see that it gives you a break down of all the components required to render a web page including: what it is, where it is, how big it is, and how long it took to load.
There are many tools on the web to help you profile your web page – check out this guide for a few more tools that you can use.

2. Save images in the right format to reduce their file size.

JPEG vs GIF screenshot.
If you have a lot of images, it’s essential to learn about the optimal format for each image. There are three common web image file formats: JPEG, GIF, and PNG. In general, you should use JPEG for realistic photos with smooth gradients and color tones. You should use GIF or PNG for images that have solid colors (such as charts and logos).
GIF and PNG are similar, but PNG typically produces a lower file size. Read Coding Horror’s weigh-in on using PNG’s over GIF’s.

3. Minify your CSS and JavaScript documents to save a few bytes.

Minification is the process of removing unneeded characters (such as tabs, spaces, source code comments) from the source code to reduce its file size. For example:
This chuck of CSS:
.some-class {
  color: #ffffff;
  line-height: 20px;
  font-size: 9px;
}
can be converted to:
.some-class{color:#fff;line-height:20px;font-size:9px;}
…and it’ll work just fine.
And don’t worry – you won’t have to reformat your code manually. There’s a plethora of free tools available at your disposal for minifying your CSS and JavaScript files. For CSS, you can find a bunch of easy-to-use tools from this CSS optimization tools list. For JavaScript, some popular minification options are JSMIN, YUI Compressor, and JavaScript Code Improver. A good minifying application gives you the ability to reverse the minification for when you’re in development. Alternatively, you can use an in-browser tool like Firebug to see the formatted version of your code.

4. Combine CSS and JavaScript files to reduce HTTP requests

For every component that’s needed to render a web page, an HTTP request is created to the server. So, if you have five CSS files for a web page, you would need at least five separate HTTP GET requests for that particular web page. By combining files, you reduce the HTTP request overhead required to generate a web page.
Check out Niels Leenheer’s article on how you can combine CSS and JS files using PHP (which can be adapted to other languages). SitePoint discusses a similar method of bundling your CSS and JavaScript;they were able to shave off 1.6 seconds in response time, thereby reducing the response time by 76% of the original time.
Otherwise, you can combine your CSS and JavaScript files using good, old copy-and-paste‘ing (works like a charm).

5. Use CSS sprites to reduce HTTP requests

CSS Sprites on Digg.
A CSS Sprite is a combination of smaller images into one big image. To display the correct image, you adjust the background-position CSS attribute. Combining multiple images in this way reduces HTTP requests.
For example, on Digg (shown above), you can see individual icons for user interaction. To reduce server requests, Digg combined several icons in one big image and then used CSS to position them appropriately.
You can do this manually, but there’s a web-based tool called CSS Sprite Generator that gives you the option of uploading images to be combined into one CSS sprite, and then outputs the CSS code (the background-position attributes) to render the images.

6. Use server-side compression to reduce file sizes

This can be tricky if you’re on a shared web host that doesn’t already server-side compression, but to fully optimize the serving of page components they should be compressed. Compressing page objects is similar to zipping up a large file that you send through email: You (web server) zip up a large family picture (the page component) and email it to your friend (the browser) – they in turn unpack your ZIP file to see the picture. Popular compression methods are Deflate and gzip.
If you run your own dedicated server or if you have a VPS – you’re in luck – if you don’t have compression enabled, installing an application to handle compression is a cinch. Check out this guide on how to install mod_gzip on Apache.

7. Avoid inline CSS and JavaScript

By default, external CSS and JavaScript files are cached by the user’s browser. When a user navigates away from the landing page, they will already have your stylesheets and JavaScript files, which in turn saves them the need to download styles and scripts again. If you use a lot of CSS and JavaScript in your HTML document, you won’t be taking advantage of the web browser’s caching features.

8. Offload site assets and features

Amazon S3Fox for Six Revisions.
Unloading some of your site assets and features to third-party web services greatly reduces the work of your web server. The principle of offloading site assets and features is that you share the burden of serving page components with another server.
You can use Feedburner to handle your RSS feeds, Flickr to serve your images (be aware of the implications of offloading your images though), and the Google AJAX Libraries API to serve popular JavaScript frameworks/libraries like MooTools, jQuery, and Dojo.
For example, on Six Revisions I use Amazon’s Simple Storage Service (Amazon S3 for short), to handle the images you see on this page, as well as Feedburner to handle RSS feeds. This allows my own server to handle just the serving of HTML, CSS, and CSS image backgrounds. Not only are these solutions cost-effective, but they drastically reduce the response times of web pages.

9. Use Cuzillion to plan out an optimal web page structure

Cuzzillion screen shot.
Cuzillion is a web-based application created by Steve Souders (front-end engineer for Google after leaving Yahoo! as Chief of Performance) that helps you experiment with different configurations of a web page’s structure in order to see what the optimal structure is. If you already have a web page design, you can use Cuzillion to simulate your web page’s structure and then tweak it to see if you can improve performance by moving things around.
View InsideRIA’s video interview of Steve Sounders discussing how Cuzillion works and the Help guide to get you started quickly.

10. Monitor web server performance and create benchmarks regularly.

The web server is the brains of the operation – it’s responsible for getting/sending HTTP requests/responses to the right people and serves all of your web page components. If your web server isn’t performing well, you won’t get the maximum benefit of your optimization efforts.
It’s essential that you are constantly checking your web server for performance issues. If you have root-like access and can install stuff on the server, check out ab – an Apache web server benchmarking tool or Httperf from IBM.
If you don’t have access to your web server (or have no clue what I’m talking about) you’ll want to use a remote tool like Fiddler or HTTPWatch to analyze and monitor HTTP traffic. They will both point out places that are troublesome for you to take a look at.
Benchmarking before and after making major changes will also give you some insight on the effects of your changes. If your web server can’t handle the traffic your website generates, it’s time for an upgrade or server migration.