Optimizing Memory

Memory is a key resource that must be managed in any optimization of a web container. The procedure is to:

  1. Measure the static and dynamic memory requirements of your application.
  2. Configure the JVMs memory limits
  3. Adjust the thread pool to constrain dynamic memory use.
  4. Tune garbage collection.

To tune memory usage, using a profiling tool like optimizeIt of jProbe can be very useful, however it can also be done simply by monitoring the memory allocated to the process by the operating system.

Measuring memory usage

Running a webapp can consume memory for:

  • Statically allocated memory during initialization.
  • Heap space allocated for Session objects per user of the webapp.
  • Stack space allocated per thread.
  • Heap space allocated for objects created during the processing of requests.

Check for memory leaks

Before optimizing your memory, it is important to establish that your webapp does not have any memory leaks. This is to say that no memory allocated when processing requests that cannot be freed when the server returns to idle. This can be determined by running the application with a constant low to medium load and monitoring the memory usage. The memory allocated should increase to a level and then stabilize. If the memory continues to grow and/or a OutOfMemory exception is eventually thrown, then the application has an object/memory leak. Such an application will not be able to run long term and the leak should be fixed before optimizing or deploying the webapp.

Note that application data caches or poor garbage collection (GC) behaviour may appear as a memory leak. If possible disable application caches or configure them to small sized in order to test the applications underlying memory requirements. The JVM may be forced to perform a GC after a fixed number of requests by the requestsPerGC attribute of HttpServer. This can be set to a low value to avoid large fluctuations in memory usage during this measurement phase:


  ... 
  100
  ...

Stack space Usage

JVMs allocate a fixed amount of stack space per thread created. The stack space is used for storing parameters and other objects associated with a method call. The more nested method calls that you application requires (deeper stacks), then more stack space is required. Typically the default stack settings for JVMs are rather generous and are allocated per thread, thus significant savings can be made by tuning this allocation.

For many JVMs, the stack space allocation is controlled with the -Xss option and the following command runs Jetty with 96kb allocated per stack:

java -Xss96k -jar start.jar

The simplest way to measure your stack requirements is to reduce the stack allocated until complex requests fail with StackOverflowException. You then need to increase your stack allocation with a good safety margin, the size of which will depend greatly on your application as some may have large variation in stack usage, specially those that use recursion.

Static & Dynamic Memory Usage

An estimate of the static and dynamic heap space usage is needed to optimize the memory allocation. This is best done by measuring memory usage under realistic steady load at several load levels. The Jetty HttpListener should be configured to have a low minimal threads setting, so that idle threads do not effect the measurements.

The following table shows some results for a simple test for memory usage using the unix ps command to determine the resident memory set size:

Active connections/threads Process size in kb kb per connection
0 23076
20 27540 224
40 29352 90
60 31868 125
100 33852 49
150 38264 88

Extrapolating from this table gives the following approximate formula for memory usagage for this webapp:

memoryRequired = 23Mb + threads * 200kb

Ideally this formula should be tested with direct measurement under all load levels.

This formula can now be used to calculate the memory requirements for your system and the JVM parameters should set to ensure that enough memory is available when the maximum number of threads are in use. For the above example, if a maximum of 500 threads are required (see below) and a 128k stack size is used, then 120MB of memory is required and the JVMs memory parameters should be configures as follows:

java -Xss128k -Xms120m -jar start.jar

Alternately, the memory formula can be used in reverse. If a known amount of physical or virtual memory is available and must not be exceeded, then the maximum number of threads can be determined.

Clustered Memory Usage.

Memory usage for a node in a cluster cannot be measured by looking at a single node. If distributed sessions or EJBs are being used, then memory used on one node may be replicated on all nodes. For example, with distributed HTTP sessions, each node must have capacity to store all the sessions for all the nodes in the cluster.

For this reason, it is often desirable to not have large homogenous clusters. Rather a cluster of clusters topology can reduce the memory and failure contingency load on each node.