A bit of conjecture here. It is possible for an object to be finalized and garbage collected even if there are references to it in local variables on the stack, and even if there is an active call to an instance method of that object on the stack! The requirement is that the object be unreachable. Even if it's on the stack, if no subsequent code touches that reference, it's potentially unreachable.
See this other answer for an example of how an object can be GC'ed while a local variable referencing it is still in scope.
Here's an example of how an object can be finalized while an instance method call is active:
class FinalizeThis {
protected void finalize() {
System.out.println("finalized!");
}
void loop() {
System.out.println("loop() called");
for (int i = 0; i < 1_000_000_000; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
System.out.println("loop() returns");
}
public static void main(String[] args) {
new FinalizeThis().loop();
}
}
While the loop()
method is active, there is no possibility of any code doing anything with the reference to the FinalizeThis
object, so it's unreachable. And therefore it can be finalized and GC'ed. On JDK 8 GA, this prints the following:
loop() called
finalized!
loop() returns
every time.
Something similar might be going on with MimeBodyPart
. Is it being stored in a local variable? (It seems so, since the code seems to adhere to a convention that fields are named with an m_
prefix.)
UPDATE
In the comments, the OP suggested making the following change:
public static void main(String[] args) {
FinalizeThis finalizeThis = new FinalizeThis();
finalizeThis.loop();
}
With this change he didn't observe finalization, and neither do I. However, if this further change is made:
public static void main(String[] args) {
FinalizeThis finalizeThis = new FinalizeThis();
for (int i = 0; i < 1_000_000; i++)
Thread.yield();
finalizeThis.loop();
}
finalization once again occurs. I suspect the reason is that without the loop, the main()
method is interpreted, not compiled. The interpreter is probably less aggressive about reachability analysis. With the yield loop in place, the main()
method gets compiled, and the JIT compiler detects that finalizeThis
has become unreachable while the loop()
method is executing.
Another way of triggering this behavior is to use the -Xcomp
option to the JVM, which forces methods to be JIT-compiled before execution. I wouldn't run an entire application this way -- JIT-compiling everything can be quite slow and take lots of space -- but it's useful for flushing out cases like this in little test programs, instead of tinkering with loops.