3

I'm testing how gc works with the classes in java.lang.ref package, just for study :)

The following is my code.

public static void main(String [] args) {

    int mb = 1024*1024;

    //Getting the runtime reference from system
    Runtime runtime = Runtime.getRuntime();

    System.out.println("##### Heap utilization statistics [MB] #####");
    ArrayList<Object> sb = new ArrayList<Object>();
    for(int i =0; i < 5000000; i++){
        sb.add(new Object());
        if( i % 1000 == 0) {
            System.out.println(i);
        }
    }

    SoftReference<ArrayList> wr = new SoftReference<ArrayList>(sb);

    // System.gc()

    //Print used memory
    System.out.println("Used Memory:"
        + (runtime.totalMemory() - runtime.freeMemory()) / mb);

    //Print free memory
    System.out.println("Free Memory:"
        + runtime.freeMemory() / mb);

    //Print total available memory
    System.out.println("Total Memory:" + runtime.totalMemory() / mb);

    //Print Maximum available memory
    System.out.println("Max Memory:" + runtime.maxMemory() / mb);
}

It results:

Used Memory:95,
Free Memory:28,
Total Memory:123,
Max Memory:247

And I undid comment on "System.gc()", and reran the code, the result was

Used Memory:1,
Free Memory:122,
Total Memory:123,
Max Memory:247

Yeah, firstly, the instance of ArrayList was collected. As I know, Instances referenced only by SoftReference is softreachable, so collected when GC is really needed because of lack of left heap space. The left space of first result of the code was about 150(free mem 28 + left max mem 124). I don't understand why the instance of ArrayList was collected.

And secondly, I ran the code with modifying:

sb.add(new Object()); -> sb.add(new StringBuffer(i));

and It resulted:

Used Memory:245,
Free Memory:2,
Total Memory:247,
Max Memory:247

Why is this different??

And lastly, I ran the code again with modifying: from

SoftReference<ArrayList> wr = new SoftReference<ArrayList>(sb);

to

WeakReference<ArrayList> wr = new WeakReference<ArrayList>(sb);

It resulted:

Used Memory:245,
Free Memory:2,
Total Memory:247,
Max Memory:247

I had guessed the instances of ArrayList were collected because the instances were referenced only by WeakReferece, so these were weakreachable. But they were not collected.

I now assume that my understanding about the way of Reference's work was incorrect.

Please anyone let me know why.

Thx ^^

trincot
  • 317,000
  • 35
  • 244
  • 286
ParkCheolu
  • 1,175
  • 2
  • 14
  • 30
  • 1
    This cannot possibly be the same code that you've actually run. Since you never remove the `sb` reference itself, the `ArrayList` would certainly not be collected. Didn't you just remove some `sb = null;` line between the second and third tests? – Dolda2000 Apr 23 '16 at 03:01
  • First thing gc will not delete an object if it still has a refrence. and you can never be sure if System.gc() will run or not it depends on the jvm. – Sumanth Jois Apr 23 '16 at 03:07
  • As Dolda has pointed out, you are holding a strong reference to the `ArrayList`. If anything, the differences you are seeing are probably due to `ArrayList` resizing itself repeatedly, which allocates progressively larger arrays during your loop. (And those arrays are what is being garbage-collected.) Also, I'm not sure I understand your question about `StringBuffer`. `StringBuffer` is much larger than a plain `Object` because it has an array inside it. Shouldn't it therefore be expected that it uses significantly more memory? – Radiodef Apr 23 '16 at 03:20
  • Please don't use StringBuffer as it was replaced more than ten years ago. This uses **a lot** more memory as you are reserving a very large `char[]` inside it. – Peter Lawrey Apr 23 '16 at 19:05
  • @PeterLawrey StringBuffer uses 16 chars with the default constructor, as does StringBuilder. Does that count as very large, or did you just mean in terms of relative space for this experiment? – AlBlue Apr 24 '16 at 09:33
  • 1
    If you do `new StringBuffer(i)` where `i` is close to `5000000` then it is going to use around 10 MB *per buffer*. Where as `new Object()` will use around 16 bytes even for large values of `i`. – Peter Lawrey Apr 24 '16 at 09:53

1 Answers1

2

The embedded question is easiest to answer: if you replace new Object() with new StringBuffer(i) with an increasing i, you are creating StringBuffer instances with an increasing capacity, so it shouldn’t come at a surprise that these objects require much more memory than a stateless Object instance.

The main question is not so easy to answer as you are presenting us a hard-to-reproduce result and in-between turn to a much-easier-to-reproduce result which suggest that either, you have changed more in your code than you have said or there were subtle changes in your testing environment in-between. In principle, both results are possible, but completely unrelated to the change you have made.

First of all, by the time you are invoking System.gc() you are holding a strong reference to the ArrayList in a local variable, so whether the additional reference is weak or soft, is completely irrelevant. In most setups and test runs, you will experience the ArrayList and the contained objects still occupying memory.

But this is not the end of the story. As discussed in “finalize() called on strongly reachable object in Java 8”, an object can get collected even if a strong reference is held, if the JVM can prove that this reference will not used. As further discussed, whether this will happen merely depends on the optimization state of the JVM and the executed code, so it’s rather unlikely to happen with your example program consisting of a sole main method which usually runs in the interpreter, but it’s not impossible.

But if this happens, this logic applies to all unused references within your method, which includes the reference to the SoftReference resp. WeakReference instance. If that Reference object itself gets collected, it’s semantic against the referent will again be irrelevant. Then, the ArrayList, the contained objects and the reference object are collected together.

If you set the sb variable to null explicitly before invoking System.gc() and invoke get() on the reference object after it, you may experience different results, but keep in mind, that System.gc() still is only a hint to the JVM and may be ignored, thus having no effect at all.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765