Search code examples
javaloopsmemory-leaksheap-memoryheap-dump

How to use less Heap Space in endless loop?


I got a problem with the memory usage of my java Application. With both Heap Space and Non Heap Space. For now I concentrate on my Heap Space.

My Application is a SocketServer, which gets Input via DataInputStream. I'm reading in the information as byte array. I got an irregular amount of Input each second, but we are speaking about a space from 400 byte to 1.000 byte per second, peaks can go higher.

As my programm is a Server it waits in an endless loop for input. Now I have the problem, that my Heap Space is climbing up over time, all 5-10 minutes it rises by 0,5MB.

I used multiple monitor apps, like jconsole and YourProfiler. After that I tried to figure out with the help of Heap Dumps, which I gnerate with jmap and analyse with Eclipse Memory Analyzer.

Now my question is, in this example code, which Option is better or rather uses less Heap Space 1 or 2?

Option 1:

while (true){
byte [] one= new byte [21]; 
do something with one;
byte [] two= new byte [50];
do something with two;
byte [] three= new byte [30];
do something with three;
}

Option 2:

byte [] one;
byte [] two;
byte [] three;
while (true){
one= new byte [21]; 
do something with one;
two= new byte [50];
do something with two;
three= new byte [30];
do something with three;
}

I don't know what happens to the three objects created in the loop. These should be local variables and only visible and accessible in the loop. But after one loop circle, the JVM would delete them and creates new one in the next circle. So there should be no memory leak, I guess?

In the second Option, the three variables are declared outside the loop, so that they will be alive for the whole time. In the loop the references of these objects changes, so there would be no reference to the old content, which means it gets deleted, respectively collected by the GC.

In both Option there will be about 4 circles for each second.

Thank you in advance for your help!

Result of JUnit Test:

enter image description here


Solution

  • The variables one, two and three are just references: they don't hold the value themselves, but instead just refer to places in the heap where the actual array object is stored.

    As such, there is no difference between the two approaches in terms of the number of objects allocated.

    Option 3: allocate the arrays outside the loop, and reuse the same arrays:

    byte [] one= new byte [21];
    byte [] two= new byte [50];
    byte [] three= new byte [30];
    while (true){
      // If necessary, zero out the arrays so that data from the previous
      // iteration is not used accidentally.
      Arrays.fill(one, (byte) 0);
      Arrays.fill(two, (byte) 0);
      Arrays.fill(three, (byte) 0);
    
      // Rest of the loop.
    }
    

    This allocates the arrays up-front, so only 3 array objects are created, rather than (3 * #iterations) array objects.

    Note that you can only use this approach if you don't leak references to the arrays, e.g. put them into a list which exists outside the body of the loop.


    To demonstrate that the memory allocation is identical in the OP's two approaches, try decompiling the code:

      public static void inLoop() {
        while (true) {
          byte[] one = new byte[21];
          byte[] two = new byte[50];
          byte[] three = new byte[30];
        }
      }
    
      public static void outsideLoop() {
        byte[] one;
        byte[] two;
        byte[] three;
        while (true) {
          one = new byte[21];
          two = new byte[50];
          three = new byte[30];
        }
      }
    

    These two methods decompile to identical bytecode:

      public static void inLoop();
        Code:
           0: bipush        21
           2: newarray       byte
           4: astore_0
           5: bipush        50
           7: newarray       byte
           9: astore_1
          10: bipush        30
          12: newarray       byte
          14: astore_2
          15: goto          0
    
      public static void outsideLoop();
        Code:
           0: bipush        21
           2: newarray       byte
           4: astore_0
           5: bipush        50
           7: newarray       byte
           9: astore_1
          10: bipush        30
          12: newarray       byte
          14: astore_2
          15: goto          0
    

    As such, the runtime memory allocation must be the same.