Java

Proactive Reductions in Memory Usage

Last updated Mar 18, 2005.

I have talked a lot in this series about how to adapt your environment (tune your heap and structure your thread and connection pools to adapt to your application). Now, it's time for a step back; let's look at how you can proactively reduce the memory foot-print of your application from an architecture and development perspective. I concentrate on two areas in this article:

Session Management

Sessions are a natural part of enterprise applications; this is the result of HTTP being a stateless protocol. In an HTTP "conversation," the client (or browser) makes a request of the Web server by opening a socket connection, GETing or POSTing a document with associated parameters, receiving a response from the Web server, and then closing the socket. At that point, it is the responsibility of the client to initiate future requests.

The server retains specific knowledge about individual clients through sessions. Session information can be passed in one of three ways:

Each of these has its associated benefits and drawbacks:

Regardless of the mechanism you choose, the end result is the receipt of some kind of "key" that you look up internally on each user request. This usually corresponds to an HttpSession object (in Java Servlet terms) that you use to store information about the user.

From a performance standpoint, realize that session information is stored on a per-user basis. Therefore, if you store too much information in your HttpSessions, the amount of information required to store this information will be multiplied by the number of users on your system at any given time.

To further complicate the issue, HTTP is a stateless protocol. It is therefore as it is the responsibility of the client to initiate requests, which begs the question: how long do we hold on to the session information, waiting for the client (user) to initiate a subsequent request? The answer relies on your business requirements, but the default on most application servers is between 20 minutes and 1 hour. Consider the case where the application server deletes session information after 60 minutes and the average user time is 15 minutes; your application server needs to maintain session information for all current users, as well as four times that amount for users who have already left the site, never to return.

Here are a few things you can do to lessen the impact on your application server:

In general, you want to store as little information as possible in your HTTP sessions. Large sessions will manifest themselves as mass memory consumers only when subjected to significant load.

One option that I like to implement is the use of Stateful Session Beans. Stateful Session Beans are stored in a cache of a specific size. As long as you size your cache appropriately to avoid excessive passivation, you can control the upper limit of memory consumption. A Stateful Session Bean cache will grow to a fixed size; when it reaches that size, it passivates (writes to disk) all sessions that it cannot hold. There's a danger: if your cache is too small, you spend more time passivating and activating (loading from disk) session information, a condition referred to as thrashing. In a thrashing condition, you spend more time managing your cache than servicing your request. But if your caches are sized appropriately, through observed behaviors, then you can gain significant benefits from the infrastructure that it provides.

Object Lifecycles

I have hosted more webinars and seminars for Quest Software than I can recall, but one of the biggest architectural considerations that we promote is defining object lifecycles in your use cases. The basic concept is what to define when objects are created and destroyed at a very high level. At any given point in time, you want to know what objects will be alive in your heap. If you push object lifecycle definition to your use cases, you gain control over objects that may linger, and litter, your heap.

From a specific and personal perspective, this means that you need to observe your application objects with the following considerations in mind:

In the best case, you will make as many objects as possible thread-safe and stateless. Recall that thread-safe objects, or more specifically thread-safe methods, are methods in classes that have no member variables that hold state in between calls. If you can identify such objects, you can make them "singletons" or define the object as a single static instance. There will be one instance of each object in your heap to service each request, so the memory footprint is almost negligible.

If you cannot make the object thread-safe, you may be able to make the object stateless. With a stateless object, you obtain an instance of the object from a pool, use it, and, when you return it to the pool, you do not require the object in subsequent requests. This means that you can obtain an object, set its properties, execute its business functions, and then reset its state before you return it to the pool. This is still far superior to creating new instances of the object on each request.

The bottom line is that we want to reduce the number of created objects on a per-request basis. Given that this is not possible for all objects, don't force it upon your architecture. Yet, use stateless and thread-safe objects whenever possible. The danger of creating objects, using them, and destroying them in the context of a single request is that you are incurring additional overhead on the JVM garbage collector.

The more dangerous situation is defining objects that span multiple requests. In the former situation, proper configuration of your heap results in collecting this object in a "minor" collection, which is relatively inexpensive. If the object lifecycle spans multiple requests, chances are that it will require a "major" collection to reclaim the object's memory (an expensive operation).

The final take-away from this section is: know your objects! Understand what objects that have to exist across multiple requests (business requirements) and refactor those that do not necessarily need to exist on a long term basis!