2
votes

I build a sample program demonstrate memory leak in java.

public class MemoryLeakTest {
     static int depth = 0;
     int number=0;
     MemoryLeakTest mobj;

     MemoryLeakTest(){
      number = depth;
      if(depth < 6500){
          depth++;
          mobj = new MemoryLeakTest();
      }
     }

     protected void finalize(){
      System.out.println(number + " released.");
     }

    public static void main(String[] args) {
        try{
             System.out.println(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
             System.out.println("Free Memory in starting "+ Runtime.getRuntime().freeMemory());
             MemoryLeakTest testObj = new MemoryLeakTest();
             System.out.println("Free Memory in end "+ Runtime.getRuntime().freeMemory());
             System.out.println(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
        }
        catch(Exception exp){}
        finally{
            System.out.println("Free Memory"+ Runtime.getRuntime().freeMemory());
            System.out.println(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
        }
    }

}

I run it by changing value of N in if(depth < N). An here is the result;

when depth is 1000

init = 16777216(16384K) used = 288808(282K) committed = 16252928(15872K) max = 259522560(253440K) Free Memory in starting 15964120 Free Memory in end 15964120 init = 16777216(16384K) used = 288808(282K) committed = 16252928(15872K) max = 259522560(253440K) Free Memory 15964120 init = 16777216(16384K) used = 288808(282K) committed = 16252928(15872K) max = 259522560(253440K)

when depth is 1500

init = 16777216(16384K) used = 288808(282K) committed = 16252928(15872K) max = 259522560(253440K) Free Memory in starting 15964120 Free Memory in end 15964120 init = 16777216(16384K) used = 288808(282K) committed = 16252928(15872K) max = 259522560(253440K) Free Memory 15873528 init = 16777216(16384K) used = 379400(370K) committed = 16252928(15872K) max = 259522560(253440K)

when depth is 6000

init = 16777216(16384K) used = 288808(282K) committed = 16252928(15872K) max = 259522560(253440K) Free Memory in starting 15964120 Free Memory in end 15692784 init = 16777216(16384K) used = 560144(547K) committed = 16252928(15872K) max = 259522560(253440K) Free Memory 15692784 init = 16777216(16384K) used = 560144(547K) committed = 16252928(15872K) max = 259522560(253440K)

when depth is 6500 (Exception in thread "main" java.lang.StackOverflowError)

init = 16777216(16384K) used = 288808(282K) committed = 16252928(15872K) max = 259522560(253440K) Free Memory in starting 15964120 Free Memory in end 15676656 init = 16777216(16384K) used = 576272(562K) committed = 16252928(15872K) max = 259522560(253440K)

My questions are;

  1. It is not calling finalize(). Is it memory leak?
  2. There is not change in free memory up to N=1000. But when N=1500 there is 2 different values for used memory at the end of the program ie 282K and 370K. Why does it so?
  3. When N=6500, JVM generates error. So why last 2 statements of try{} are executed.
5
Observation, I found that JVM start releasing memory when I create huge amount of object like 1-2 lakhs. It means it is not memory leak but GC start releasing memory when it actually needs. Moreover, It is releasing object top on the heap first like it'll release object 9000 first then 8999 then 8998. interesting..Amit Kumar Gupta
Observation 2: even after running GC, getHeapMemoryUsage giving higher used space.Amit Kumar Gupta
You can see stackoverflow.com/questions/6470651/… : really useful for memory leaks in Java !Val

5 Answers

1
votes

Your program won't "leak" as Java will take care of anything "dangling" out there. That's the benefit of a garbage-collected language.

But what you do have is a StackOverFlow error. Basically, the stack (which is the chain of functions you're in, and how deep that is) is much MUCH smaller than the heap. The heap is "more or less" the size of main memory. Each thread's stack is much much smaller. Basically you're reaching that limit by doing your "Depth" thing.

If you want to test "leaks" (or the idea that you won't have any eventually) try something more like this:

public class MemoryLeakTest {
     int number=0;
     public MemoryLeakTest mobj;

     MemoryLeakTest(int num){
        number = num;
     }

     protected void finalize(){
        System.out.println(number + " released.");
     }

    public static void main(String[] args) {
        try{
             System.out.println(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
             System.out.println("Free Memory in starting "+ Runtime.getRuntime().freeMemory());
             MemoryLeakTest first = new MemoryLeakTest(0);  // Keep a reference to one of them
             MemoryLeakTest current = first;
             for(int i = 1; i < Int.Parse(args[0]); i++) // forgive me, Java's been a while.  This may be C#.  But parse the first arg for your number of objects
             {
                 current.mobj = new MemoryLeakTest(i);
                 current = current.mobj;
             }
             System.out.println("Free Memory in end "+ Runtime.getRuntime().freeMemory());
             System.out.println(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
        }
        catch(Exception exp){}
        finally{
            System.out.println("Free Memory"+ Runtime.getRuntime().freeMemory());
            System.out.println(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
        }
    }

}

That will give you a "chain" of objects all in memory until first goes out of scope.

1
votes

It is not calling finalize(). Is it memory leak?

No there is no memory leak you always keep accessible reference to your testObj object and that's the reason why finalizewill never be called in your application.

All you do in your application is to create a huge object graph.

Here you can find an explanation how to create a real memory leak in java.

1
votes

It is not calling finalize(). Is it memory leak?

Finalize is not guaranteed to be called, it is called when the garbage collector collects the given object but the objects are not guaranteed to be collected before the execution ends.

There is not change in free memory up to N=1000. But when N=1500 there is 2 different values >for used memory at the end of the program ie 282K and 370K. Why does it so?

I think it depends on the execution of the garbage collector and the moments that it gets executed.

When N=6500, JVM generates error. So why last 2 statements of try{} are executed.

This is because you're not catching the exception since StackOverflowError inherits from Error that is not part of the Exception inheritance branch but rather is a brother of Exception, anyway you have no code in the catch, the last two methods of your try are not being executed because the exception has been thrown.

In summary you didn't produce a memory leak, memory leaks happen in java when you have references to objects that are reachable (directly or indirectly) from the execution flow at some point, for example you store objects in a collection that you can reach, or singletons.

The garbage collector itself is smart enough to free object graphs that are not reachable from the program at all.

Hope I could make it clear.

1
votes

Already most of the answers explained difference between StackOverflowError and memory leak.

  • There is not change in free memory up to N=1000. But when N=1500 there is 2 different values for used memory at the end of the program ie 282K and 370K. Why does it so?

  • it is because every time you create new object and previous obj become unreachable(no references, overriding reference) and hence can be freed if required.

So far simplest example to make jvm run out of memory (not leak).

public class PrintSeries {

private static String COMMA = ",";
private StringBuilder buildStream;// = new StringBuilder();

public static void main(String[] args) {
    System.out.println(new PrintSeries().convert(10));
    System.out.println(new PrintSeries().convert(1000000000));
}

private String convert(int n) {
    buildStream = new StringBuilder();

    while (n > 1) {
        buildStream.append(n-- + COMMA);
    }
    buildStream.append(n);
    return buildStream.toString();
   }
}
  • output

    10,9,8,7,6,5,4,3,2,1 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2882) at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390) at java.lang.StringBuilder.append(StringBuilder.java:119) at com.cctest.algotest.string.PrintSeries.convert(PrintSeries.java:17) at com.cctest.algotest.string.PrintSeries.main(PrintSeries.java:10)

0
votes

This is not evidence of a memory leak. The program is throwing StackOverflowError not OutOfMemoryError. In fact, that is going on is that the constructor is calling itself recursively, and when the number of recursive calls exceeds some large number (between 6,000 and 6,500), you run out of stack space.

It is not calling finalize(). Is it memory leak?

No. The finalize() method is most likely not being called because the GC has not run. And it has not run because you haven't filled the heap. And even if that is not the real explanation, there is no guarantee that the finalize() method will ever be called. The only absolute guarantee you have is that finalize() will be called before the object's memory is reused by the JVM.

There is not change in free memory up to N=1000. But when N=1500 there is 2 different values for used memory at the end of the program ie 282K and 370K. Why does it so?

I'm not sure why that happens, but I don't think it indicates anything significant. (There are all sorts of things that happen under the hood in a JVM that can be sources of non-determinacy in things like memory allocation and usage patterns.)

When N=6500, JVM generates error. So why last 2 statements of try{} are executed.

The statements in the finally is always executed, unless the JVM terminates abruptly. When the StackOverflowError is thrown, it propagates like any other exception, and can be caught and recovered from (in some cases).