OutOfMemoryException : Memory leak in the java class ObjectOutputStream and ObjectInputStream

In this article I will tell you how I found that there is a memory leak in the java class ObjectOutputStream and ObjectInputStream causing an OutOfMemoryException; Where exactly it is and how to deal with it and fix it.

Recently I had to improve the performance of one of my programs: it was using too much of the memory causing at some point a OutOfMemoryException.
I used The Eclipse Memory Analyzer to track down this problem and to correct it.

I quickly found the origin of the problem : it was in the intern behaviour of the ObjectOutputStream and ObjectInputStream class.

Lets put it in the context :

My program is a log reader and analyzer. It gather multiples logs from multiples location and provide a GUI to display them into time-synchronized, filterable, tabs. To archive this task, and with a modularity will, I wrote a full abstractive layer (which will be the subject of a further article) intended to represent the presence of a network between the differents parts (server and client, reading module, GUI module and analyzer module).

This layer is completly based on the ObjectOutputStream and ObjectInputStream class and a huge amound of object are serialized and deserialized through it (one object by log entry).

The origin of the OutOfMemoryException:

The Object(Out/In)putStream have a knowned internal bug resulting to a memory leak.

The basic internal behaviour of those classes when we use the writeObject(Object) and readObject() methods is to keep in memory the written and read objects to avoid unneccesary processing time when the same object is sent or read multiple times.

Of course it will generate a lot of memory kept inside those classes. So there is another methode : “writeUnshared(Object)” and “readUnshared()”.

They have the same use but won’t keep in memory the object sent or read. It will use less memory but add a bit of processing time is you send or read the same object multiple times.

But here is the bug. Let’s take a peak to the intern code:

What happend when we serialize an object?

This is the method that really handle your object:

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // handle previously written and non-replaceable objects
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // check for replacement object
            Object orig = obj;
            Class cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class repCl;
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

the interesting operation are at the lines 16, 19, 58, 61, 68, 70, 72 and 74. They are the lignes where the object is sent to the writing method. let’s have a peak to one of those method.

    private void writeClass(Class cl, boolean unshared) throws IOException {
        bout.writeByte(TC_CLASS);
        writeClassDesc(ObjectStreamClass.lookup(cl, true), false);
        handles.assign(unshared ? null : cl);
    }

They all look like that. If we look closer we can see this operation : “handles.assign(unshared ? null : cl);”.

So! if we want to use the unshared functionality we send “null” to the handles? aka to the internal class that keep in memory the already sent Objects? But maybe this “null” parameter is handled in this new method. let’s read it :

        
			int assign(Object obj) {
            if (size >= next.length) {
                growEntries();
            }
            if (size >= threshold) {
                growSpine();
            }
            insert(obj, size);
            return size++;
        }

Here it is interesting :
We can see that the “null” possibility of the parameter isn’t used, in fact the array containing the already-writen object are always grown THEN populated with a “null” reference…

Here is our memory leak, it’s not a lot when just a few objects are serialized because we recover our memory when the ObjectOutputStream is grabaged.

But it is still a problem : in my case I never destroy it : it is the only way to communicate with the other side of the abstraction layer and it won’t be destroyed because that would mean losing the connection. And I send billions of object through it, so this tiny memory leak quickly grow and use more memory than all the rest of the program.

Of course the problem is exactly the same for the ObjectInputStream.

How to avoid this behaviour:

There is two way to avoid this.
The first and obvious one is to destroy the Object(Out/In)putStream couple and create again. But that can cause a lot of problem and require more computing time to create a new connection.
The second solution is to call the method “reset()” on your ObjectOutputStream: “This will disregard the state of any objects already written to the stream. The state is reset to be the same as a new ObjectOutputStream. The current point in the stream is marked as reset so the corresponding ObjectInputStream will be reset at the same point.” (extracted from the java documentation).

So to summarize:

  • The ObjectOutputStream and ObjectInputStream grow themself each time they process an Object.
  • Sending / receiving with “unshared” don’t avoid memory to be used, it only reduce the used memory.
  • To avoid the memory to be overused we need to call from time to time the “reset” method of the ObjectOutputStream class. doing so also reset the ObjectInputStream state.

Now you can design your software in order to avoid it to be too heavy.

Your imagination is your limit ;)

4 thoughts on “OutOfMemoryException : Memory leak in the java class ObjectOutputStream and ObjectInputStream

  1. Pingback: Fuite de mémoire dans les classe ObjectOutputStream et ObjectInputStream | Laboratoire Java - Paris

Leave a Reply

Your email address will not be published. Required fields are marked *