18

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 eligible 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 :).

Andrei Ciobanu
  • 12,500
  • 24
  • 85
  • 118
  • 13
    `b` isn't garbage collected. It isn't at all set. – Bozho Jul 21 '10 at 13:19
  • 4
    You don't show a `go` method. Is that meant to be a call to `easyMethod`, which is never called in this sample? – Dave Costa Jul 21 '10 at 13:19
  • 4
    Seems to me the person doing the interview didn't even understood that the code given to you has not the slightest beginning of coherence. Give it to your compiler, and have fun (and that is only a foreword). I hope you didn't succeed this interview. Elsewhere you're entering a terrible mess. – Riduidel Jul 21 '10 at 13:22
  • Please fix your question there is no b ... – mathk Jul 21 '10 at 13:55
  • Saying that b is GC is non sense – mathk Jul 21 '10 at 14:07

7 Answers7

19

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.

corsiKa
  • 81,495
  • 25
  • 153
  • 204
  • and the two objects are a1 and a3 – willcodejavaforfood Jul 21 '10 at 13:30
  • For the sake of correctness I would change "Two objects get garbage collected" to "Two objects are *eligible* to be garbage collected" – matt b Jul 21 '10 at 13:38
  • @Justin I lol'd. :) @matt true - my comment reflects this point, but my summary does not. Fixed. – corsiKa Jul 21 '10 at 13:40
  • I think it does not matter of `A.go()` is `A.easyMethod()` or not. In regards to GC, the object referred to by a3 is orphaned by the call and thus eligible for GC. Unless more code is given `b` is dead code. – rioki Jul 21 '10 at 13:43
  • @Sean you're absolutely right about a3 being orphaned, and about b being dead code. :-) – corsiKa Jul 21 '10 at 13:46
  • -1 All three objects are unreachable and, therefore, eligible for garbage collection at that point. – J D Jan 05 '13 at 10:18
  • @Jon sorry, but a2 is not null. A reference that points to the same object that a2 points to is nulled in the method but it has no effect on the one that is outside the method. At thus point the interviewer may politely ask you to go :-) – corsiKa Jan 05 '13 at 17:53
  • @corsiKa "a2 is not null". You are correct but missing my point. Garbage collectors cannot see variables like `a2`. They see only memory references in registers, on the stack, in global variables and from block of memory to others. A compiler will not generate code that retains an unnecessary reference to the object referred to by `a2` simply because that variable is still in scope in the source code so `a2` being not `null` is irrelevant. – J D Jan 06 '13 at 11:19
  • 1
    @Jon you don't know that `a2` is an unnecessary variable. It might be utilized after the `// some other code` comment. Regardless, the garbage collector CAN see that `a2` is on the main thread, and that the object `a2` points to is not eligible for garbage collection. You cannot make any guarantees about what optimizations a compiler will or will not make. You can only trust it is JLS compliant and follow what would logically happen in your code according to the spec. So yes, I'm missing your point about how an object with a reference from the main thread is unreachable and eligible for GC. – corsiKa Jan 06 '13 at 19:34
  • @corsiKa: "the garbage collector CAN see that `a2` is on the main thread". Garbage collectors cannot see variables like `a2`. They see only memory references in registers, on the stack, in global variables and from block of memory to others. The GC may or may not see a reference to the object `a2` refers to. – J D Jan 06 '13 at 20:30
  • @Jon sorry, that's simply incorrect. The GC keeps track of every reference. The GC *will* see that the `a2` reference, and will know that the object it points to is not eligible for collection. I'm quite interested to see what citation you may have that would cause `a2` to be collected in this case. – corsiKa Jan 06 '13 at 21:03
  • @corsiKa "citation". Any basic text about garbage collection, like Jones' GC Handbook. http://gchandbook.org/ – J D Jan 06 '13 at 21:47
  • +1 for the extension of the code example to demonstrate your (our :-) point. – Péter Török Jan 07 '13 at 08:53
7

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.

Community
  • 1
  • 1
Péter Török
  • 114,404
  • 31
  • 268
  • 329
  • +1 For mentioning that Java is pass by value. It just never passes Objects, just their references. I understand why it is confusing to some, but I think it's something every (Java) developer should know. – corsiKa Jul 21 '10 at 13:41
  • -1 All three objects are unreachable and, therefore, eligible for garbage collection at that point. – J D Jan 05 '13 at 10:19
  • @JonHarrop, that's just plain wrong. Right before `// Some other code`, one of the objects is still reachable via the reference `a2`. I read your comment to @Mark's answer, which is sort of correct in pointing out that the original question was not worded correctly, i.e. "eligible for garbage collection" is not a precise term. Nevertheless, your comment here is clearly incorrect. – Péter Török Jan 05 '13 at 15:02
  • Agree with @JonHarrop. If a2 is not used by // Some other code, then the object referenced by a2 is indeed eligible for garbage collection. Many runtimes/GCs will use liveness information about variables, and will consider variables which are not used for the remainder of the method as 'dead'. This is the reason the method GC.KeepAlive exists in the .Net framework (http://msdn.microsoft.com/en-us/library/system.gc.keepalive.aspx), to ensure that a variable which is no longer used by the method is kept alive until the call to GC.KeepAlive. – joncham Jan 05 '13 at 17:59
  • 1
    @joncham, indeed **if** `a2` is not going to be used by `// Some other code`, the object referenced by it is eligible for garbage collection. So it is fine to mention that the object in question **may** be eligible for garbage collection, depending on factors we don't know (i.e. what would `// Some other code` do). However, the only objects surely eligible for garbage collection at this point are the other two. Moreover, @JonHarrop stating that the object in question is unreachable before `// Some other code` is obviously wrong, as it is reachable via `a2`. – Péter Török Jan 05 '13 at 19:41
  • @PéterTörök "...reachable via `a2`". Such statements are nonsensical. GCs see references in registers and on the stack, not variables like `a2`. But you're right that replacing the comment with code that refers to `a2` could keep the object it refers to reachable for longer. However, it makes no sense to discuss points in the source code in the context of the GC because there is no equivalent in the compiled code. – J D Jan 05 '13 at 21:07
  • @JonHarrop, I fully agree that it makes no sense to discuss points in the source code in the context of the GC. – Péter Török Jan 05 '13 at 21:31
  • @PéterTörök Imagine all of the code in the body of the main function were actually inside one branch of an `if` statement. The lexical scope of those variables would then be that branch of the `if` statement and not the whole function. What would your model predict the reachability of the object that `a2` references after the `if` statement, i.e. outside the lexical scope of `a2`? If you assume all locals are stored in the function's stack frame then `a2` keeps the object reachable. If you assume variables are pushed and popped individually over their lexical scope then it is unreachable. – J D Jan 07 '13 at 21:52
7

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.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • At the point of // some other code . there is only a2 who can be used. a1 and a3 is null. – mathk Jul 21 '10 at 14:06
  • The objects initially referenced by `a1` and `a3` have no way of being referenced again at the point of "some other code", and so they can be GC'ed. The same can't be said of the object referenced by `a2`. This doesn't depend at all on later code. – Justin Ardini Jul 21 '10 at 14:22
  • @Justin: I never meant to imply otherwise, but I understand the source of your confusion and so have edited it to hopefully reflect better what I was trying to say. – Mark Peters Jul 21 '10 at 14:57
  • Thanks, much clearer! I had interpreted your phrasing "completely depends" as meaning the code snippet has no effect on whether any of the objects would be GC'ed. – Justin Ardini Jul 21 '10 at 15:04
  • 1
    Yeah, it goes back to the necessary vs sufficient conditions. Not referencing a1/a2/a3 in "some other code" was a sufficient condition for GC eligibility, but certainly not necessary, as indeed a1/a3's original referents were GC eligible either way. "Completely depends" obviously doesn't convey that properly. – Mark Peters Jul 21 '10 at 15:46
5

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.

Dave Costa
  • 47,262
  • 8
  • 56
  • 72
  • 3
    "If an in-scope variable references an object, then it cannot be garbage-collected". Liveness analysis in the compiler removes dead variables from stack frames before the end of their scope. If they aren't on the stack then they aren't global roots to the GC so what they refer to can be garbage collected. – J D Jan 05 '13 at 10:25
5

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.

John
  • 289
  • 1
  • 2
3

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.

J D
  • 48,105
  • 13
  • 171
  • 274
  • Well yes, the mental model this interview question is based on is a gross simplification of real life - just as most of the stuff in any schoolbook I have ever read. Shall we thus throw away all schoolbooks as "incorrect"? – Péter Török Jan 05 '13 at 15:12
  • 1
    *"The point in the code you have marked is the end of the entire program."* - at least if you process the comment `// Some other code` as if you were a compiler, i.e. skip it. To me the comment is meant to indicate that there is some more code there, which *may* use any of the variables / fields defined so far, but the details of exactly what happens there is not relevant in the context of this question. So IMHO the conservative answer would in fact be to assume that all variables are further referenced there (guarded by a null pointer check as necessary). – Péter Török Jan 05 '13 at 15:19
  • "the mental model this interview question is based on is...". Undefined. – J D Jan 05 '13 at 23:00
  • "process the comment...as if you were a compiler". When discussing garbage collection it seems prudent to treat what has been described as "code" from the point of view of a compiler. But I agree that changing the code given in the question to some other code can change its behaviour. – J D Jan 05 '13 at 23:05
  • Re "Undefined" - honestly, do you expect an interviewer to provide a full formal specification with each of his questions? Is this how you regularly communicate with other human beings? ;-) – Péter Török Jan 06 '13 at 16:43
  • "do you expect an interviewer to provide a full formal specification with each of his questions". If they want their questions answered on the basis of a model that cannot make any useful predictions and bears no resemblance to reality, yes. Until then, you don't know what the interviewer wanted. You're just speculating. Maybe you're right. Maybe not. My point is that we do not know. – J D Jan 06 '13 at 17:43
  • Well, most of the answerers gave largely the same answer to this question, and even you and Mark Peters seem to be in agreement with the rest of the party about 2 out of the 3 objects in question. Which to me suggests that several people managed to independently associate to (more or less) the same mental model from this question, and this mental model provided them with (largely) the same predictions. Thus IMHO saying that this is "a model that cannot make any useful predictions and bears no resemblance to reality" is quite an exaggeration. – Péter Török Jan 06 '13 at 20:42
  • 1
    @Jon you're putting the burden of proof on the wrong ox here. You're saying "they must illustrate that a2 is used below the comment for it to not be eligible". We're saying "you must assume it is not eligible until you can prove it is not used below the comment". Because you can't prove it's *not* being used, we must *assume* it is being used. – corsiKa Jan 06 '13 at 22:31
  • @Jon you'll be happy to know that I've revised my answer to hopefully accommodate your concerns. – corsiKa Jan 06 '13 at 22:48
  • @corsiKa "You're saying..." That is not what I am saying at all. You're completely missing the point of what I'm saying. My point is that your answer is based upon many flawed assumptions about how compilers and garbage collectors work. They don't do what you are implying they do. Moreover, you have not stated your assumptions. – J D Jan 07 '13 at 00:24
  • @PéterTörök "most of the answerers gave largely the same answer to this question". You can easily construct a counter example. Consider a simple compiler that converts to SSA, introducing new variables for everything and pushes all of them onto the stack for the duration of their scope. Then the original `a1`, `a2` and `a3` are still in scope so all three objects are still referred to from the stack so nothing is eligible for garbage collection (if we assume there is a direct correspondance between a point in the source and in the compiled form, which there is not). – J D Jan 07 '13 at 00:26
  • 1
    @Jon yours actually make more assumptions than mine does. Mine assumes that until we have evidence of an optimization that none is made, and that the element is not eligible for collection until the end of the method (which at the point we are concerned with has not yet occured). You assume that an element will be flagged for garbage collection early as an optimization (which is most likely true, but is still an assumption nevertheless). – corsiKa Jan 07 '13 at 00:32
  • FWIW, there is a virtual machine and garbage collector that actually works like that. It is called HLVM and I wrote it. http://www.ffconsultancy.com/ocaml/hlvm/ – J D Jan 07 '13 at 00:32
  • 1
    @corsiKa "You assume..." Only for one of my examples. My point is that you can choose from a spectrum of assumptions and obtain results ranging from nothing is eligible to everything is eligible. – J D Jan 07 '13 at 00:36
  • My point is that the main assumption most of the answerers made is simply the following: "eligible for GC" is actually meant to say "reachable via a live reference". Since all of us have seen similar interview/test questions and they always mean the same, I don't think this is a strong assumption. You (and Mark) are right in that this question is using imprecise terms and you *can* interpret it differently, moreover compilers, JVMs and GCs in real life *may* apply clever optimizations which complicate issues. – Péter Török Jan 07 '13 at 08:28
  • You are right in that our answers didn't give the *complete* picture; however IMHO you are wrong to call these answers *incorrect* and to downvote. To me this is similar to declaring that Newton's theory of mechanics is wrong, because it doesn't account for relativistic effects. Yes, strictly speaking you would be right; however, in practically all real life situations this is academic nitpicking. – Péter Török Jan 07 '13 at 08:41
  • 1
    "in practically all real life situations this is academic nitpicking". On the contrary, the implied assumptions in the other answers are completely useless in practice and in real life precisely because they make no testable predictions whatsoever. The effects cannot be observed. That is obviously nothing like Newtonian mechanics which can and has been tested and is known to be very accurate in most circumstances. My gripe with this question and most of the answers is that it is wildly misleading. For example, can you cite a single real VM/GC that actually works the way you assume? – J D Jan 07 '13 at 21:02
  • FWIW, HLVM and .NET on Windows Phone 7 retain more dead references on the stack than your assumed model whereas desktop .NET and OCaml retain fewer dead references on the stack than your assumed model. As I have shown, you can justify all answers from "nothing is eligible" to "everything is eligible" so I don't understand what is special about the particular point you chose? Surely the only correct answer is to explain that there is no answer because the question is flawed? – J D Jan 07 '13 at 21:10
  • 1
    This is the best answer to this. It might be needed to emphasize that there isn’t even a guaranty that the objects in question are ever created. To anyone having doubts (@corsiKa, Péter Török), I recommend reading [finalize() called on strongly reachable object in Java 8](http://stackoverflow.com/questions/26642153) or [JLS §12.6.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.6.1): “*Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable*” – Holger Jan 11 '17 at 12:27
2

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.

user207421
  • 305,947
  • 44
  • 307
  • 483
mathk
  • 7,973
  • 6
  • 45
  • 74