Wednesday, May 3, 2023

Quiz yourself: Collecting the Java garbage


Given the following

class Car implements Serializable {
  class Engine implements Serializable { }
  private Engine e;
  public void Car() {
    e = new Engine();
  }
  public Engine getEngine() { return e; }
}

and

List<Serializable> l = new ArrayList<>();
var c = new Car(); // line n1
var e = c.getEngine();
l.add(0, c);
l.add(0, e);
c = null; // line n2
l.set(1, 1); // line n3
e = null; // line n4
l.set(0, 0); // line n5

When will the Car instance created at line n1 become eligible for garbage collection? Choose one.

A. After line n2
B. After line n3
C. After line n4
D. After line n5
E. After the code throws an exception and leaves the method containing this fragment

Answer. This question investigates some foundational knowledge for understanding garbage collection and the idea of reachability. Depending on your expertise and attention to detail, you might be surprised that this question does not investigate inner classes in any depth. The question also investigates methods that break naming conventions and the declaration of constructors as well as the behavior of some of the methods of java.util.List.

Quiz Yourself, Java Garbage, Oracle Java Certification, Oracle Java Prep, Java Preparation, Java Career, Java Skills, Java Jobs, Java Preparation

Look at how the code executes by starting from the top of the first fragment shown.

First, the code instantiates an ArrayList and stores a reference to it in the variable l, which is of type List<Serializable>. Because both the Car and Engine classes are serializable, the list will accept both these types if you try to add them.

Next, the code creates a Car object. Soon the code will add it to the list at position zero. However, look at the construction of the Car. At a quick glance, you might assume that the construction uses the following constructor:

public void Car() {
    e = new Engine();
  }

Look closer: This is not a constructor. It’s a method with an egregiously poor name!

It’s likely that it was intended to be a constructor, but somebody’s fingers typed void. A constructor must not declare a return type and must have a name that exactly matches the class to be initialized. Therefore, if you provide a return type, as is the case with this code, you have defined a method that simply happens to have the same name as the class.

At this point, you might be throwing your hands up in horror exclaiming that this is a stupid trick question. Well, we sympathize; when we first saw this, we were pretty frustrated too, and we had a bit of a discussion about how “the smart people will get it wrong” (because if that had been a constructor, all kinds of cool things would happen and that makes test takers expect the question to be about those cool things). This, we protested, would make the question a negative discriminator. That’s test-writer jargon for a question that test takers are more likely to get wrong if they’re better prepared or smarter.

However, the reality is twofold.

◉ This kind of error will not be flagged by the compiler (it also doesn’t cause a warning with the default configuration of IntelliJ IDEA), and the resulting code will run too, albeit with the wrong results. Therefore, if you were to make this mistake, finding it and fixing it will be you and the debugger against the world.

◉ The second point is that attention to detail always matters with programming. You might protest “but you knew what I meant” to a human, but that won’t ever cut it with a compiler.

So, with that clarified, continue the investigation.

In view of the above, you now know that the constructor that initializes the Car is actually the default constructor. Importantly, this will do nothing to initialize the field Engine e, so it remains at the default value of null.

After the construction of the Car, the code calls c.getEngine(). This call returns null, and that’s stored in the variable e.

The next two steps call add(0, _). This two-argument version of add stores values at the given index, moving items to the higher index positions as needed to make space. In this case, the items are added to the very front of the list. At this point, you have a null in position zero and a Car in position one. The Car started at position zero but was pushed down to make space for the null.

Note that some lists throw an exception when a null value is added, but this is not the case with an ArrayList, so option E is looking unlikely.

At this point, variable c refers to the Car object and so does index one of the list. No Engine objects have been created, but position zero of the list and the variable e both contain null.

Line n2 overwrites the variable c with null. At this point, the only reference left to the Car object is at index one of the list. Because a reference remains, the object is not yet eligible for garbage collection. Therefore, option A is incorrect.

Line n3 overwrites the reference stored at index one of the list. The value written is the number 1. This is permitted, because it will be autoboxed to an Integer that implements Serializable. Again, no exception is thrown, so option E still looks unlikely.

Immediately after this set operation, there are no references left to the Car object. That makes it eligible for garbage collection. That tells you the correct answer to this question is option B and options C, D, and E are incorrect.

Line n4 writes null over the existing null in variable e. Line n5 changes the null at index zero of the list to the number 0. This is permitted and does not throw an exception.

To recap, no exceptions are thrown, and the Car object is eligible for garbage collection after line n3. This confirms that the correct answer is option B and not option E.

Conclusion. The correct answer is option B.

Source: oracle.com

Related Posts

0 comments:

Post a Comment