General Considerations
We have touched on most of the major performance-related aspects that are found in the various J2EE specifications, but there are three things that dramatically affect performance that are the natural side effects of running an application server. Because application servers can service multiple simultaneous requests and because thread creation is expensive, application servers have to maintain a pool of threads that handle each request. Some application servers break this thread pool into two: one to handle the incoming requests and place those in a queue and one to take the threads from the queue and do the actual work requested by the caller. Regardless of the implementation, the size of the thread pool limits the amount of work your application server can do; the tradeoff is that there is a point at which the context-switching (giving the CPU to each of the threads in turn) becomes so costly that performance degrades.
The other performance consideration is the size of the heap that the application server is running in. We already saw that it needs to maintain caches, pools, sessions, threads, and your application code, so the amount of memory you allocate for your application server has a great impact on its overall performance. The rule-of-thumb is to give the application server all the memory that you can afford to give it on any particular machine.
Finally, you must consider tuning the garbage collection of the heap in which your application server is running. As you load things into memory (and unload them), your heap will soon fill uprequiring that garbage collection free memory for your application to continue. In the Sun JDK version 1.3.x, which ships with most production application servers, it defines two types of garbage collection: minor and major.
Minor collections are performed by a process called copying that is very efficient, whereas major collections are performed by a process called mark compact, which is very burdensome to the Virtual Machine. The heap is broken down into two partitions: the young generation, in which objects are created and hopefully destroyed; and the old generation, in which objects are retired to a more permanent place in memory. The young generation uses copying to perform minor collections, whereas the old generation uses mark compact to perform major collections, so your goal when tuning garbage collection is to size the generations to maximize minor collections and minimize major collections.