Never Assume Anything

Friday, July 31, 2009 by Matt Warman

My client in Cincinnati is having issues with one of their Java based web applications. The application is throwing an out of memory error. One of the vendor's application development personnel traced it back to a local byte array object. He said "it can’t be the problem because it is created in a method. I don't have to null the object because it is garbage collected". In Java, you are taught that any variables created in a method call persist only for the life of that method call. When the method has completed, all objects are out of scope, so they are to be garbage collected. Java uses implicit object creation and destruction, so the application developer can focus on the problem. Like any rule though, there are exceptions. If your local variable is referencing an external object, and that reference is still live, your local object still persists in memory. Since the method has been garbage collected, the object will stay in memory and not be freed until you restart the server. The best example of this is creating a database connection object. If you don’t call the close method and null it, the object will persist until the JVM is shut down. This called a stale connection. Even though you may have created the connection in a method, the object doesn’t get collected. Look at the following code:

package test;
public class MemTest
{
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.9);

 public byte[] func()
 {
    byte[] data = new byte[dataSize];
    System.out.println("func: byte array created in func");
    System.out.println("func: Total Mem=" + Runtime.getRuntime().totalMemory()
               + " Free Mem=" + Runtime.getRuntime().freeMemory());
    return data;
 }

 public static void test()
 {
    MemTest jmp = new MemTest();
    System.out.println("Max Mem=" + Runtime.getRuntime().maxMemory()
              + " dataSize=" + jmp.dataSize);
    System.out.println("Total Mem=" + Runtime.getRuntime().totalMemory()
              + " Free Mem=" + Runtime.getRuntime().freeMemory());
     byte[] data1 = jmp.func();
     System.out.println("byte array returned in jmp.func, size=" + data1.length);
     System.out.println("Total Mem=" + Runtime.getRuntime().totalMemory()
                + " Free Mem=" + Runtime.getRuntime().freeMemory());
      jmp = null; // this does not do anything as data1 still has a reference to the byte[] returned from jump.func()
//   data1 = null; // if data1 is not set to null here to remove to the reference to the byte[], data2=jump2.func() will hit OutOfMemory exception
      MemTest jmp2 = new MemTest();
      byte[] data2 = jmp2.func();
      System.out.println("byte array returned in jmp2.func, size=" + data2.length);
      System.out.println("Total Mem=" + Runtime.getRuntime().totalMemory()
            + " Free Mem=" + Runtime.getRuntime().freeMemory());
 }

 public static void main(String[] args)
 {
    System.out.println("--test1--");
    test();
    System.out.println("--test2--");
    test();
    System.out.println("--test3--");
    test();
    System.out.println("--test4--");
    test();
 }
}

If you run this code, it will throw an out of memory exception. Uncomment the data1= null statement and run again. This application is the 1.4 JDK, and the newer JDKs are doing a better job at handling this situation. I also know that there are 2 things to remember in application development, don’t assume anything, and rules were meant to broken. I still have some testing to do, but I am sure my Cincinnati client will be happy.


Comments for Never Assume Anything

Thursday, August 27, 2009 by wow:
hey:)

Leave a comment





Captcha