views:

438

answers:

3

I was reading Java Platform Performance and section A.3.3 worried me.

I had been working on the assumption that a variable that dropped out of scope would no longer be considered a GC root, but this paper appears to contradict that.

Do recent JVMs, in particular Sun's 1.6.0_07 version, still have this limitation? If so, then I have a lot of code to analyse...

I ask the question because the paper is from 1999 - sometimes things change, particularly in the world of GC.

A: 

Would you really have that much code to analyse? Basically I can only see this being a significant problem for very long-running methods - which are typically just the ones at the top of each thread's stack.

I wouldn't be at all surprised if it's unfixed at the moment, but I don't think it's likely to be as significant as you seem to fear.

Jon Skeet
+2  A: 

This code should clear it up:

public class TestInvisibleObject{
  public static class PrintWhenFinalized{
    private String s;
    public PrintWhenFinalized(String s){
      System.out.println("Constructing from "+s);
      this.s = s;
    }
    protected void finalize() throws Throwable {
      System.out.println("Finalizing from "+s);
    }   
  }
  public static void main(String[] args) {
    try {
        PrintWhenFinalized foo = new PrintWhenFinalized("main");
    } catch (Exception e) {
        // whatever
    }
    while (true) {
      // Provoke garbage-collection by allocating lots of memory
      byte[] o = new byte[1024];
    } 
  }
}

On my machine (jdk1.6.0_05) it prints:

Constructing from main

Finalizing from main

So it looks like the problems has been fixed.

Note that using System.gc() instead of the loop does not cause the object to be collected for some reason.

Rasmus Faber
In answer to your final statement on System.gc(): Calling System.gc() does not guarantee an immediate collection - it's just a request. It may be completely ignored.
Leigh
Indeed, the -XX:+DisableExplicitGC option has been added to the JVM to turn the call into a NO-OP. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6200079 makes it clear this option will never be turned on by default, sadly...
Bill Michell
In an earlier iteration of the example I constructed a PrintWhenFinalized-object that was completely unreachable (it was created and abandoned in another method). That one was collected when calling System.gc(), so it was not because the call was ignored.
Rasmus Faber
Even more interesting: Calling System.gc() prevents the invisible object from ever being collected! Adding a System.gc() call right before the while-loop, I never see the finalizing message.
Rasmus Faber
Rasmus, that's not completely true :)The code above have to be modified a little after the catch block:System.gc();List l = new LinkedList();System.out.println("starting the loop");while (true) { byte[] o = new byte[1024]; l.add(o);}Voila - it works :)
Lazarin
+1  A: 

The article states that:

... an efficient implementation of the JVM is unlikely to zero the reference when it goes out of scope

I think this happens because of situations like this:

public void doSomething() {  
    for(int i = 0; i < 10 ; i++) {
       String s = new String("boo");
       System.out.println(s);
    }
}

Here, the same reference is used by the "efficient JVM" in each declaration of String s, but there will be 10 new Strings in the heap if the GC doesn't kick in.

In the article example I think that the reference to foo keeps in the stack because the "efficient JVM" thinks that is very likely that another foo object will be created and, if so, it will use the same reference. Thoughts???

public void run() {
    try {
        Object foo = new Object();
        foo.doSomething();
    } catch (Exception e) {
        // whatever
    }
    while (true) { // do stuff } // loop forever
}

I've also performed the next test with profiling:

public class A {

    public static void main(String[] args) {
     A a = new A(); 
     a.test4();
    }

    public void test1() {  
        for(int i = 0; i < 10 ; i++) {
           B b = new B();
           System.out.println(b.toString());
        }
        System.out.println("b is collected");
    }

    public void test2() {
        try {
         B b = new B();
            System.out.println(b.toString());
        } catch (Exception e) {
        }
        System.out.println("b is invisible");
    }

    public void test3() {
        if (true) {
         B b = new B();
            System.out.println(b.toString());
        }
        System.out.println("b is invisible");
    }

    public void test4() {
     int i = 0;
        while (i < 10) {
         B b = new B();
            System.out.println(b.toString());
            i++;
        }
        System.out.println("b is collected");
    }

    public A() {
    }

    class B {
     public B() {
     }

     @Override
     public String toString() {
      return "I'm B.";
     }
    }
}

and come to the conclusions:

teste1 -> b is collected

teste2 -> b is invisible

teste3 -> b is invisible

teste4 -> b is collected

... so I think that, in loops, the JVM doesn't create invisible variables when the loop ends because it's unlikely they will be declared again outside the loop.

Any Thoughts??

bruno conde