To get performance boosts out of Java™ applications, many developers choose to develop their own object management solutions to replace the default garbage collector. A common solution is object pooling. Creating a Java object is somewhat expensive; when you create objects once and reuse them many times, you reduce the overhead. This makes sense, particularly in server-side applications, as the same server code is normally executed repeatedly. For applications that run in a single Java Virtual Machine (JVM), such a solution is normally simple. In a multi-tiered architecture, however, managing object instances across multiple machines and multiple JVMs can be quite complicated.
In this article, I'll introduce a framework for managing Java objects in a multi-tiered J2EE architecture. This framework is based on standard interfaces defined by Servlet, JSP, EJB, JMS, and Struts technologies. Therefore, it is not tied to any specific vendor's solutions. Unlike many other object management solutions that require applications to explicitly release object instances when they are no longer needed, this framework uses the concept of scopes to release object instances automatically. This feature dramatically reduces the risk of memory leaks, which are a serious problem when the default garbage collector is disabled. Overall, this framework provides a practical and easy-to-use solution to improve performance in a distributed J2EE environment.
Java object creation and garbage collection are expensive operations. Excessive object creation can be a main cause of performance problems in Java applications. This problem tends to be worse in server-side programs like servlets. Often, you have many local variables that are short-lived within a method, but the method itself is invoked repeatedly and frequently. As a result, objects are frequently created and then garbage collected. This object churning can hurt performance.
Object pooling is one solution to this problem. The idea is to have a collection of objects -- usually of the same type -- created and stored in a pool. If an object is needed, it is retrieved from the pool and not created afresh. After the object finishes its job, it is returned back to the pool and can be reused later. The object pool needs to keep track of the state of each object and to be thread safe. You can find many implementations of Java object pooling, such as Apache's Common Pool (see Resources). Apart from implementation details, object pools generally have the interface illustrated in Listing 1.
Listing 1. Object Pool API
public Object getObject(Class clazz); public void returnObject(Object obj); |
The getObject() method retrieves an instance of the given class from the pool. The returnObject() method releases the object back to the pool.
The object pooling approach improves performance in most cases. However, it is not without cost. First of all, developers have to pay the price of explicitly calling returnObject(object) when the object is no longer needed. This seems to defeat the whole purpose of automatic garbage collection, which is an important (perhaps the most important) feature of the Java language. Secondly, the developer risks memory leakage in the application. Objects can stay around if they are not returned to the pool explicitly. These two tradeoffs might scare developers away from adopting the pooling technique.
It would be nice if Java developers could enjoy the performance boost of object pooling without having to worry about explicitly releasing objects. Fortunately, this is feasible in most server-side J2EE applications. The framework I discuss in this article provides a practical solution.
Take a look at a server-side application that has a servlet, JSP page, and EJB component. Using the MVC and Façade patterns, Figure 1 shows a typical design.
Figure 1. A server-side application
Upon receiving a request, a servlet calls a method on a session bean, which then invokes a method on an entity bean. After the session call returns, the servlet then forwards the request to a JSP page. Throughout the entire flow, objects are created as indicated in Figure 1. Different objects may have different lifespans. Some objects are intended for only one request cycle; after the request is completed, they are no longer useful. Other objects may have longer lifespans: for instance, they may live through an HTTP session or even the entire application. If you identify the intended scope of each object, you might specify to automatically release objects at the end of the scope. For example, object1 and object4 in the diagram are both bound to the request scope. After the request is completed, both objects can be released automatically. object2 is bound to a transaction scope. After the transaction is terminated, it can be released automatically. object3 is bound to the application scope and lives through the entire lifetime of the application.
To accomplish this effect, resolve these two issues: First, modify the object pool so that it understands the concept of scope. Second, make an automatic notification mechanism available to indicate when a scope has ended.
I'll use the term object manager in this article to distinguish the framework from a conventional object pool. Much like an object pool, an object manager is responsible for getting objects from a pool and returning objects back to the pool. In addition, an object manager understands the concept of scope. Listing 2 shows the API for the object manager.
Listing 2. The ObjectManager API
public interface ObjectManager {
/**
* Retrieve an object instance of the give class from the object pool
* for the given scope, identified by a scope type and a scope key.
*
* @param clazz the class
* @param scopeType the type of the scope
* @param scopeKey the key to identify the scope
*
* @return an object instance retrieved from the object pool
*/
public Object getObject(Class clazz, int scopeType, Object scopeKey);
/**
* Release an object back to the object pool.
*
* @param object the object to be released
*/
public void releaseObject(Object object);
/**
* Release all objects of the given scope, identified by a scope type
* and a scope key.
* @param scopeType the type of the scope that objects bound to
* @param scopeKey the key to identify the scope
*/
public void releaseAll(int scopeType, Object scopeKey);
}
|
To retrieve an object from the object pool, provide a scope type and a scope key. The object manager associates the scope information to each object being retrieved. When a releaseAll() method is called, the object manager can then identify objects associated with the given scope and properly release them back to the pool.
Six scope types are commonly used in most server-side J2EE applications:
- Transaction
- Request
- HTTP session
- Application
- Global
- None
Developers can add additional scope types if they need them for their applications. The scope types I discuss in this article are defined in Listing 3.
Listing 3. Scope type definition
public class ScopeTypes {
public final static int TRANSACTION = 1;
public final static int REQUEST = 2;
public final static int HTTP_SESSION = 3;
public final static int APPLICATION = 4;
public final static int GLOBAL = 5;
public final static int NONE = 6;
}
|
A transaction scope extends over the entire body of a demarcated transaction. The scope starts when a transaction begins. Create a unique scope key at that time. The scope ends when the transaction commits or rolls back. At that point, all objects bound to the transaction scope are automatically released back to their pools.
A request scope corresponds to a servlet request scope; it starts immediately after the container calls a servlet to process a request. Create a unique scope key at the same time, and end it just before the servlet finishes the process. At that point, all objects bound to this scope are automatically released back to their pools.
An HTTP session scope corresponds to the lifespan of an HTTP session. It starts when a new HttpSession is created. Create a unique scope key at that time. End it when the session is destroyed or expired. At that point, all objects bound to this session scope should be automatically released back to their pools.
An application scope encompasses the entire lifetime of an application. It starts when an application is deployed to the application server. A unique scope key should be created at that time. The scope ends when the application is stopped or removed from the application server. At that point, all objects bound to this application scope are automatically released back to their pools.
A global scope is the largest scope. Objects tagged with it are never released.
The none scope is used for objects that are not pooled. Objects tagged this way are created through their own object constructors every time and are released by the Java garbage collector. The object manager does not manage them at all.
Scope keys and event notification
To use the object manager defined in the last section and to identify scopes, you'll need to use scope keys. In this section, I discuss when to create scope keys and how to store them. Having the object manager understand scopes addresses one of the issues raised at the beginning of this article. The other issue is how to automatically release objects based on their scopes. To free developers of responsibility for explicitly releasing objects, you need to notify the object manager when scopes end so that it can properly release objects. Servlet and EJB containers provide the necessary event trigger mechanism to handle the scope changes. Look at how to use this event trigger mechanism in all types of scopes.
According to the EJB 2.0 specification, session bean classes that implement javax.ejb.SessionSynchronization have to implement afterBegin(), beforeCompletion(), and afterCompletion(). The EJB container calls afterBegin() directly after a transaction begins and
beforeCompletion() right before a transaction completes. The EJB container calls afterCompletion()immediately after a transaction is completed or rolled back.
- Use
afterBegin()to create a unique scope key for a transaction scope. Store the scope key in the session bean itself for future retrieval. - Use
afterCompletion()to notify the object managers that a transaction scope has ended and to release the corresponding objects.
Depending on the technologies used, you can generate events for request scope in different ways. I'll look at four of the most popular technologies in use today in this section.
Servlet 2.4.
Version 2.4 of the Java Servlet specification added a listener for request-related events. javax.servlet.ServletRequestListener has a requestInitialized() method and a requestDestroyed() method.
requestInitialized()- The container calls this method when a servlet request is about to go into scope.Use it to create a unique scope key for this request scope. Store the scope key in the request object itself for future retrieval.requestDestroyed()- The container calls this method when a servlet request is about go out of scope. Use it to notify the object manager that a request scope is about to end and to release the corresponding objects.
Servlet 2.3 or older.
Version 2.3 of the Java Servlet specification does not provide any listening capability for request-related events. The best way to deal with this lack is to implement the creation of a scope key and the notification logic in a superclass that extends from javax.servlet.http.HttpServlet. Then you can extend all application servlets from this superclass. Implement the creation of a scope key and the notification logic in the service() method of this superclass.
Struts 1.0.
As in Servlet 2.3, in Struts 1.0 create a scope key and implement the notification logic in a superclass that extends from org.apache.struts.action.ActionServlet. You can use the process() method to create a scope key and implement the notification logic.
Struts 1.1
Since Struts 1.1, org.apache.struts.action.RequestProcessor has been added to process logic that the ActionServlet performs as it receives each servlet request from the container.
Use the RequestProcessor.process() method to create a scope key and implement the notification logic.
According to the Servlet 2.3 specification, listeners that implement javax.servlet.http.HttpSessionListener need to implement sessionCreated() and sessionDestroyed().
sessionCreated()- The container calls this method after a new session is created. Use it to create a unique scope key for this session scope. Store the scope key in the session object itself for future retrieval.sessionDestroyed()- The container calls this method after a session is invalidated or expired. Use it to notify object managers that a session scope has ended (or is about to end) and to release the corresponding objects. (Under the Servlet 2.4 spec, the second method is called when a session is about to be invalidated; the update does not affect this framework.)
According to the Servlet 2.3 specification, listeners that implement javax.Servlet.ServletContextListener need to implement contextInitialized() and contextDestroyed().
contextInitialized()- The container calls this method when the application is initialized and ready to serve. Use it to create a unique scope key for this application scope. Store the scope key in the servlet context object itself for future retrieval.contextDestroyed()- The container calls this method when the application is about to be shut down. Use it to notify object managers that an application scope has ended and to release the corresponding objects.
No event is necessary for this scope, because objects associated with it are never released.
No event is necessary for this scope because objects associated with it are released by the garbage collector rather than object managers.
Putting it together: An example
In this section, I use an example to further explain the introduced concepts. The sample code demonstrates how to use the object manager to manage a request scope in a Struts 1.1 application.
MyRequestProcessor, shown in Listing 4, intercepts every Struts action request so request scope keys can be created before a request is processed and objects can be released after the request is processed.
Listing 4. A RequestProcessor class implementation
public class MyRequestProcessor extends RequestProcessor{
public void
process(HttpServletRequest request, HttpServletResponse response)
throws ServletException {
// before each request is processed, create a request key
String requestScopeKey = UniqueIDGenerator.GetID();
request.setAttribute("requestScopeKey ", requestScopeKey);
try {
// process the request
super.process(request, response);
} finally {
// after the request is processed, release all objects
// bound to this request scope
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
objMgr.releaseAll(ScopeTypes.REQUEST, requestScopeKey);
}
}
}
|
In Listing 5, AnyAction represents any Struts action; it shows how you can get an object from the object manager.
Listing 5. An action class implementation
public class AnyAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
// ...
String requestScopeKey = request.getAttribute("requestScopeKey");
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
obj = objMgr.getObject(obj.getClass(), ScopeTypes.REQUEST,
requestScopeKey);
// ...
}
}
|
Everything discussed above works well as long as the scope stays within the same component or container. This is true in normal situations. For instance, a request scope starts and stops in a servlet container. A transaction scope starts and stops in an EJB container. If this kind of design works for you, you can stop here and ignore the rest of the article. However, in many cases, scopes may cross multiple components. Consider a case in which an EJB session bean method is always called in a Struts action. Some objects in this session bean method might belong to an HTTP request. That is, an HTTP request started in a Struts action can actually go into an EJB session bean. In the following sections, I address the issues in such a distributed environment.
A typical J2EE application may have multiple components that run on different JVMs or even on different physical machines. In a clustered environment, one logical component may consist of more than one physical node, with each node running on a JVM. In this distributed computing environment, illustrated in Figure 2, each JVM has at least one object manager.
Figure 2. A distributed computing environment
Different object managers may manage objects belonging to the same scope. For instance, object1, object2, object3, and object4 in Figure 2 are all bound to the same request scope. After the request scope has ended, their individual object managers must release them. Therefore, the notification mechanism must work not only within one JVM, but also across multiple JVMs. Use the Java Messaging Service (JMS) to fulfill this goal.
When you have to notify multiple object managers of an event at the same time, a publish-subscribe paradigm is best suited for your communication needs. The publish-subscribe model is supported in JMS, which uses topics for broadcasting messages. To use JMS, bundle each object manager with a JMS listener. As illustrated in Figure 3, when a scope ends, a message is published to a topic. The message contains a scope type and a scope key. All JMS listeners that subscribe to this topic will then review the message and ask their object managers to release objects bound to the scope.
Figure 3. Using JMS to coordinate object management
To see how this works in more detail, look at some sample code. Listing 6 shows how to broadcast messages that contain scope information.
Listing 6. A sample implementation of broadcasting messages
// ---------------------------------------------------
// a scope is ended at this point
// the scope type is stored in scopeType
// the scope key is stored in scoreKey
// ctx is a JNDI context
TopicConnectionFactory tcf = (TopicConnectionFactory)
ctx.lookup("ObjectManagerJMSConnection");
TopicConnection tc = tcf.createTopicConnection();
tc.start();
TopicSession ts = tc.createTopicSession(false, 1);
Topic t = (Topic) ctx.lookup("ObjectManagerJMSTopic");
TopicPublisher tp = ts.createPublisher(t);
TextMessage tm = ts.createTextMessage();
tm.setText(scopeType + ":" + scopeKey);
tp.publish(tm);
|
Listing 6 shows the standard way to publish a JMS message on a topic. The names of TopicConnectionFactory and Topic should reflect the names set in your JMS server. The message is a text message containing a scope.
Listing 7 shows how to implement a JMS listener that can be bundled with an object manager.
Listing 7. A MessageListener class implementation
public class ObjectManagerListener implements MessageListener {
private Context ctx = null;
private TopicConnection tcon = null;
private TopicSession tsession = null;
private TopicSubscriber tsubscriber = null;
/**
* Constructor.
*
* @param ctx a JNDI context
* @throws NamingException
*/
public ObjectManagerListener(Context ctx) throws NamingException{
this.ctx = ctx;
}
/**
* Starts the listener.
*
* @throws Exception
*/
public void start() throws Exception {
TopicConnectionFactory tconFactory =
(TopicConnectionFactory) ctx.lookup("ObjectManagerJMSConnection");
tcon = tconFactory.createTopicConnection();
tsession = tcon.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = (Topic) ctx.lookup("ObjectManagerJMSTopic");
tsubscriber = tsession.createSubscriber(topic);
tsubscriber.setMessageListener(this);
tcon.start();
}
/**
* Stops the listener.
*
* @throws Exception
*/
public void stop() throws Exception {
this.close();
}
/**
* Upon receiving a message, the listener asks its object manager
* to release objects in the scope specified in the message.
*
* @param msg A message containing a scope type and a scope key.
*/
public void onMessage(Message msg){
try {
String msgText;
if (msg instanceof TextMessage) {
msgText = ((TextMessage)msg).getText();
} else {
msgText = msg.toString();
}
StringTokenizer st = new StringTokenizer(msgText, ":");
if (st.countTokens() == 2) {
String scopeType = st.nextToken();
String scopeKey = st.nextToken();
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
objMgr.releaseAll(scopeType, scopeKey);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() throws Exception {
tsubscriber.close();
tsession.close();
tcon.stop();
tcon.close();
ctx = null;
}
}
|
ObjectManagerListener is a JMS listener. When starting, it connects to the JMS server and listens on the topic. Note that in the start() method, the names of the TopicConnectionFactory and Topic should reflect the names set in your JMS server. Upon receiving a text message, if the message is in the right format, the ObjectManagerListener extracts the scope type and the scope key from the message and asks the object manager to release all objects bound to this scope.
Context for passing scope information
In a distributed environment, scopes cross multiple components. Therefore, scope keys created in one component must be passed to other components so the components can use the keys to obtain objects bound to the same scopes. The only way to pass scope keys across multiple components is through method calls. To facilitate this approach, a context object is needed to store scope information. For every request, a context object is created. This object stores keys for the current request scope, HTTP session scope, and application scope. The object is then passed to other components through method calls. When a transaction starts, the scope key for this transaction is added to this context object. When a transaction ends, the corresponding scope key is removed. Listing 8 defines the API for this object.
Listing 8. RequestContext API
public interface RequestContext {
/**
* Get the scope key for the given scope type.
*
* @param scopeType
* @return
*/
public Object getScopeKey(int scopeType);
/**
* Set the scope key for the given scope type.
*
* @param scopeType
* @param scopeKey
*/
public void setScopeKey(int scopeType, Object scopeKey);
}
|
Since the context object needs to be passed across components, the object should be serializable.
With JMS and RequestContext, you can now modify the previous example. In addition to creating request scope keys, MyRequestProcessor needs to get the application scope key and session scope key and store them in a RequestContext object before a request is processed. After the request is processed, it sends a JMS message to indicate that this request scope has ended. Listing 9 shows how to do this.
Listing 9. A modified implementation of the RequestProcessor class
public class MyRequestProcessor extends RequestProcessor{
public void
process(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// assume when application started, the application id was generated
// and stored in ServletContext with attribute name " appScopeKey"
String appScopeKey =
request.getServletContext().getAttribute("appScopeKey");
// session scope key
String sessionScopeKey = request.getSession().getId();
// request scope key
String requestScopeKey = UniqueIDGenerator.GetID();
RequestContext ctx = RequestContextFactory.getInstance();
ctx.setScopeKey(ScopeType.APPLICATION, appScopeKey);
ctx.setScopeKey(ScopeType.HTTP_SESSION, sessionScopeKey);
ctx.setScopeKey(ScopeType.REQUEST, requestScopeKey);
// store the request context in request
request.setAttribute("requestContext", ctx);
try {
// process the request
super.process(request, response);
} finally {
// after the request is processed, release all objects bound
// to this request scope
TopicConnectionFactory tcf = (TopicConnectionFactory)
ctx.lookup("ObjectManagerJMSConnection");
TopicConnection tc = tcf.createTopicConnection();
tc.start();
TopicSession ts = tc.createTopicSession(false, 1);
Topic t = (Topic) ctx.lookup("ObjectManagerJMSTopic");
TopicPublisher tp = ts.createPublisher(t);
TextMessage tm = ts.createTextMessage();
tm.setText(ScopeTypes.REQUEST + ":" + requestScopeKey);
tp.publish(tm);
}
}
}
|
The modified AnyAction class in Listing 10 demonstrates how to retrieve the request scope key from the RequestContext object and pass the RequestContext object to EJB components.
Listing 10. A modified implementation of the Action class
public class AnyAction extends Action{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
// ...
RequestContext ctx =
(RequestContext) request.getAttribute("requestContext");
// get the request scope key from the context object
String scopeKey = ctx.getScopeKey(ScopeType.REQUEST);
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
obj =
objMgr.getObject(obj.getClass(), ScopeTypes.REQUEST, scopeKey);
// call methodY of sessionBeanX and pass the context object
// as an parameter
sessionBeanX.methodY(params, ctx);
// ...
}
}
|
Instead of retrieving scope keys from HttpServletRequest as shown in Listing 5, the sample code in Listing 10 retrieves the RequestContext object from HttpServletRequest. It then retrieves the scope keys from the RequestContext object. When calling the remote or local interfaces of EJB objects, it passes the RequestContext object as a parameter.
I've added SessionBeanX, outlined in Listing 11, to demonstrate how to use the RequestContext object inside EJB components.
Listing 11. A SessionBean class implementation
public class SessionBeanX implements SessionBean{
public void methodY (Object params, RequestContext ctx) {
// ...
// get the request scope key from the context object
String scopeKey = ctx.getScopeKey(ScopeType.REQUEST);
ObjectManager objMgr = ObjectManagerFactory.GetInstance();
obj = objMgr.getObject(obj.getClass(), ScopeTypes.REQUEST, scopeKey);
// ...
}
}
|
To retrieve the scope information, each method of the SessionBeanX class must have the parameter RequestContext. As shown in Listing 10, when the EJB client calls the remote or local interface, it must pass the RequestContext object, which contains information about current scopes. The RequestContext object inside the bean method can then obtain the keys of current scopes -- through methodY(), for instance.
This article proposes a framework for managing object instances in J2EE applications. The main advantage of this framework is that it automatically releases object instances using the event-triggering mechanism defined in J2EE. This not only frees the developer from responsibility for explicitly releasing objects in code, but also reduces the risk of memory leakage. The first half of the article has targeted a system in which the server-side code runs in a single JVM. The second half of the article targeted an environment in which the server-side code runs in multiple JVMs. Throughout the article, I provided sample code to illustrate concepts. Performance benchmarks are beyond the scope of this article and will be published in the future.
- Check out Java Performance Tuning, 2nd Edition, Jack Shirazi (O'Reilly & Associates, January 2003); Chapter 4, "Object Creation," is available online.
- Read "Java
performance programming, Part 1: Smart object-management saves the day," Dennis M. Sosnoski (JavaWorld, November 1999; developerWorks, December 1999) to learn how to reduce program overhead and improve performance by controlling object creation and garbage collection.
- Read "Make object pooling simple," Karthik Rangaraju (JavaPro, November 2002) to learn how to implement object pools.
- Visit Apache's Commons Pool for a generic API as well as some general-purpose implementations.
- Get the final release of the Servlet 2.3 Specification, available from the Java Community Process.
- Find the final version of the Servlet 2.4 specification, also available at the Java Community Process.
- Download the EJB 2.0 specification at Sun's Web site.
- If you are still using Struts 1.0, check out the Struts 1.0.2 API.
- If you are using Struts 1.1, see the Struts 1.1 API.
- Go to Sun's Web site to learn about the Java Message Service (JMS).
- Check out "The evolution of a high-performing Java virtual machine," W. Gu, N. A. Burns, M. T. Collins, and W. Y. P. Wong (IBM Systems Journal, 2000). The section entitled "Object allocation and heap management" explains various techniques used in IBM Java Virtual Machines to improve the performance of object allocation and heap management.
- Get Jinsight, an IBM alphaWorks tool for visualizing and analyzing the execution of Java programs.
- Visit developerWorks Web Architecture zone for articles covering various Web-based solutions.
- Find articles about every aspect of Java technology in the developerWorks Java technology zone.
- Browse for books on these and other technical topics.
- Also, for a complete listing of free tutorials that focus on Web and Java topics, see the Web architecture zone tutorials page and the Java technology zone tutorials page from developerWorks.
Zhengrong Tang has more than 15 years of experience in software development and specializes in J2EE, XML, AOP, and Perl. He currently serves as chief research architect at CTB/McGraw-Hill. Zhengrong Tang holds a Master of Science degree in computer science from the University of Pittsburgh. Contact him at ztang@zenovate.com.




