开发者

Interview question: Objects eligible for garbage collection

Give the following code:

class A {
    Boolean b;
    A easyMethod(A a){
        a = null;
        return a;
    }
    public static void main(String [] args){
        A a1 = new A();
        A a2 = new A();
        A a3 = new A();
        a3 = a1.easyMethod(a2);
        a1 = null;
        // Some other code 
    }
}

The question is how many objects are eligib开发者_JAVA百科le for garbage collection right before // Some other code.

Then correct answer is (at least that's the interviewer answer): 2 - the Boolean b because it's a wrapper and a1 .

Can you please me explain me why a2 and a3 aren't being garbage collected ?

LATER EDIT:

  • Ok, I think I get it now. It was a bit confusing at first, but now i am sure the interviewer was wrong. My initial mistake was that at first I didn't consider that Java is pass by value only, so it's impossible to make a2 null from inside a function that take "a2" as a parameter, because that a2 is actually a copy of a2.
  • The part with the Boolean b was indeed quite obvious.

Thanks for an answer, I will send some interview feedback after that :).


Assuming go is supposed to be easyMethod it works like this

class A {
    Boolean b;
    A easyMethod(A a){
        a = null; // the reference to a2 was passed in, but is set to null
                  // a2 is not set to null - this copy of a reference is!
        return a; // null is returned
    }
    public static void main(String [] args){
        A a1 = new A(); // 1 obj
        A a2 = new A(); // 2 obj
        A a3 = new A(); // 3 obj
        a3 = a1.go(a2); // a3 set to null and flagged for GC - see above for why
        a1 = null; // so far, a1 and a3 have been set to null and flagged
        // Some other code 
    }
}

Two objects are eligible for garbage collection (a1 and a3). b is not because it's only a reference to null. No Boolean was ever made.

To get around the inane subtleties of what // Some other code might be, I instead posit the question be reworded into the following:

Prdict and explain the following output:

class A {
    int i;
    A(int i) { this.i = i; }
    public String toString() { return ""+i; }
    A go(A a){
        a = null; // the reference to a2 was passed in, but is set to null
                  // a2 is not set to null - this copy of a reference is!
        return a; // null is returned
    }
    public static void main(String [] args){
        A a1 = new A(1); // 1 obj
        A a2 = new A(2); // 2 obj
        A a3 = new A(3); // 3 obj
        a3 = a1.go(a2); // a3 set to null and flagged for GC - see above for why
        a1 = null; // so far, a1 and a3 have been set to null and flagged

        test(a1);
        test(a2);
        test(a3);

    }
    static void test(A a) {
        try { System.out.println(a); } 
        catch(Exception e) { System.out.println((String)null); }
    }
}

And output:

c:\files\j>javac A.java

c:\files\j>java A
null
2
null

And the followup is that at that point, a1 and a3 were eligible for GC, and a2 was not.

The lesson from this question is that "Passing an object reference to a method and setting that reference to null does not cause the original reference to be nulled". That's the piece of knowledge the interviewer was attempting to test.


Provided a1.go(a2) is actually meant to be a1.easyMethod(a2), the answer is indeed 2, but not the ones you listed. As Bozho rightly pointed out, b is not initialized, so it doesn't refer to any object. The two objects eligible for garbage collection at the point of the comment are the ones originally referenced by a1 and a3.

a1 is obviously nulled out, and a3 is reassigned to the return value of a1.easyMethod(a2), which is null. However, a2 is not affected by the method call, as Java is pass by value, so only a copy of the reference a2 is passed to the method. Even though the copy is set to null, that does not affect the value of the original a2.


For a2's original referant it actually completely depends on what happens in "some other code". If "some other code" doesn't use a2 or a3, then the original a2 object is eligible for garbage collection.

That's because the runtime doesn't have to care about lexical scope. It just needs to know that an object can never be referenced again. Therefore, if "some other code" doesn't utilize a2 or a3, the object they point to can never be referenced again and so is already available for garbage collection.


First of all the interviewer is wrong about the Boolean -- there is no such object created by this code so there's nothing to be garbage collected.

It is incorrect to speak of variables like b and a2 as being garbage collected. Objects are garbage-collected, not variables. If an in-scope variable references an object, then it cannot be garbage-collected. Simplistically, it's only when an an object is no longer referenced by any variable that it can be garbage collected.

So we have three instances of A being created in this code. They start out referenced by a1 etc. but since variable references change I'll refer to the object instances as A1, A2, and A3. Since you haven't shown a definition of the go method I'm going to assume it's meant to be a call to easyMethod.

Since the variable a1 is reassigned to null, nothing is pointing to instance A1, so it can be garbage-collected.

Since the variable a2 is never reassigned (the assignment in easyMethod does not affect the original variable), instance A2 cannot be garbage-collected.

Since easyMethod always returns null and a3 is assigned the result of that method, nothing is pointing to instance A3, so it can also be garbage-collected.


Can you please me explain me why a2 and a3 aren't being garbage collected ?

Because a2 and a3 are not objects. They are variables. Variables are not collectable for the simple reason that they are not allocatable.


The question is how many objects are eligible for garbage collection right before // Some other code.

The question is nonsensical.

Garbage collectors act at run-time on the information available there. Reachability is determined by the global roots stored in registers, on the thread stacks and in global variables. The contents of registers and the stacks are the culmination of many stages of compilation that completely mangle the code. The concepts of lexical scope and line numbers from the source code no longer exist so it is nonsensical to ask questions about what the GC might see at certain points in the source code because those points do not exist in the world of the GC.

So we must first assume a direct correspondence between the source code and the generated code or we cannot even make sense of the point in the code that the question refers to. I cannot think of any working VM that actually does this in practice and, in fact, Java probably has high-level language constructs that cannot even be compiled to bytecode in this way.

Next, we must assume a model for the way local references are kept on the stack. Rather than assuming some ill-defined model in order to derive a random answer I'm going to show that the choice of model allows us to arrive at answers ranging from "nothing is eligible for GC" to "everything is eligible for GC". This full range of justifiable answers really highlights just how bad that question is.

Consider a simple model where intermediate values (the results of all subexpressions as well as variables) are pushed onto the stack until the end of the function. Simple compilers and virtual machines like HLVM and .NET on Windows Phone 7 actually work like this in practice even though this retains references for longer than necessary. With this model, each new A() pushes a reference onto the stack until the function returns so all three are reachable from the stack at the point in question and, therefore, nothing is eligible for garbage collection.

Consider a model where references are removed from the stack at the first point where they are never read from again. This is closer to how production virtual machines like .NET and OCaml work except they keep local references in registers when possible and spill to pre-allocated entries in the function call's stack frame otherwise, overwriting dead locals to minimize the size of the stack frame. With this model, all references are dead at the point in question so none are reachable and, therefore, all three of the newly allocated objects are eligible for garbage collection as well as the args array and all of the strings it references.

So we have not only shown that answers ranging from nothing to everything can be justified but we have even cited real working virtual machines and garbage collectors that implement these models so our predictions are testable.

If I were given that question in an interview I would test the interviewer by explaining some of this. If they reacted well by expressing an interest in learning then I would still be interested in the job. If they reacted badly by trying to defend their ill-defined assumptions and untestable predictions then I would not want to work with them.


Your question is quite confused.

The garbage collector works on objects, not on variables. So when you say that a2 is eligible for GC it mean nothing. You should say the object referenced by a2 at line N is eligible for GC at the line N+M.

In your example you only have 3 objects being instantiated. It is the first create A and the last create A instance that are eligible for GC.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜