Monday, October 14, 2013

JAXBContext memory leak

Last Friday we had an Out Of Memory exception on one of our production WebLogic servers. The server only has 2 applications running on it, so at least the job of narrowing down the culprit would be easy. Or so we thought (note to self: find a better application monitoring/profiling solution!).

6 hours and many cups of coffee later, I realized that the developer that had written this application was using JAXB to marshal / un-marshal XML from a web service end point. As per the API example, the developer was creating a JAXB context for every message in this manner:

StringWriter sw = new StringWriter();
JAXBContext jaxbContext = JAXBContext.newInstance(myObject.getClass());
Marshaller marshal = jaxbContext.createMarshaller();
marshal.marshal(myObject, sw);

A new JAXB context was getting created every single time a web service message was sent/received! There is a known memory leak issue when using the JAXB context in this manner. Check out the While One Fork blog for more information.

I ended up creating a JAXBContextFactory class that allows the JAXBContext to be cached and reused. Find the code below. We redeployed the code and voila - the memory issues disappeared!
package my.mypackage.jaxb;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

public class JAXBContextFactory {
    private static JAXBContextFactory instance = new JAXBContextFactory();
    
    private static final Map< String, JAXBContext > instances = new ConcurrentHashMap< String, JAXBContext >();
 
    private JAXBContextFactory() {
    }
 
    /**
     * Returns an existing JAXBContext if one for the particular namespace exists, 
     * else it creates an instance adds it to a internal map.
     * @param contextPath the context path
     * @throws JAXBException exception in creating context
     * @return a created JAXBContext
     */
    public JAXBContext getJaxBContext(final String contextPath) throws JAXBException {
     JAXBContext context = instances.get(contextPath);
        if (context == null) {
            context = JAXBContext.newInstance(contextPath);
            instances.put(contextPath, context);
        }
        return context;
    }
 
 
    /**
     * Returns an existing JAXBContext if one for the particular namespace exists,
     * else it creates an instance adds it to a internal map.
     * @param contextPath the context path
     * @throws JAXBException exception in creating context
     * @return a created JAXBContext
     */
    public JAXBContext getJaxBContext(final Class contextPath) throws JAXBException {
        JAXBContext context = instances.get(contextPath.getName());
        if (context == null) {
            context = JAXBContext.newInstance(contextPath);
            instances.put(contextPath.getName(), context);
        }
        return context;
    }
 
    /**
     * Get instance.
     * @return Instance of this factory
     */
    public static JAXBContextFactory getInstance() {
        return instance;
    }
}


Here is how to use it in your application:
StringWriter sw = new StringWriter();
JAXBContextFactory factory = JAXBContextFactory.getInstance();
JAXBContext context = factory.getJaxBContext(MyClass.class);
Marshaller marshal = jaxbContext.createMarshaller();
marshal.marshal(myObject, sw);

No comments:

Post a Comment