If I remember correctly, in Java one can write
new JFrame();
without saving the JFrame
reference in any variable. This program will display a GUI window on screen and leave it open, until the program is closed. (Please correct me if this premise is false).
Theoretically, we could think that since the JFrame
is now unreachable from user code, the GC should at some point free the object. And as a result, the associated OS resources (the GUI window, etc.) will be freed as well.
However, if my understanding is correct - that program is a valid and working Java program (albeit a pretty useless one), and it will not crash or behave in strange ways at any point (again correct me if this premise is wrong).
It seems that the GC will not collect the JFrame
object even though there aren't any references to it, or at least it will not cause the associated OS resources to be freed.
I would like to understand: How is this possible?
Does the constructor of
JFrame
savethis
anywhere in order to not be collected? In the source code forJFrame
's superclass java.awt.Frame (methodnoteFrame
) the frame seems to be saved in a weak references queue. But since these are weak references, that doesn't seem to explain thingsIs it in fact the case that the GC does eventually free the
JFrame
- however, the finalizer method ofJFrame
doesn't free the associated OS resources, thus the GUI window stays open?Any other explanation?
Please note that this is a follow up question to a more theoretical question I posted on SE.SE. This question is different because it relates specifically to the Java implementation details.
A JFrame
that is only created via new JFrame()
without any additional actions will not get opened on the screen and not prevented from garbage collection. Only a frame connected to a display device will be referenced from the AWT implementation and can’t get garbage collected before it has been explicitly disconnected.
Which can be verified with the following code:
public static void main(String[] args) {
check(new JFrame(), "just creating a JFrame", x -> {});
check(new JFrame(), "creating and connecting a JFrame", JFrame::pack);
check(Frame.getFrames()[0], "calling dispose()", Frame::dispose);
}
private static <T> void check(T obj, String description, Consumer<T> action) {
System.out.println(description);
action.accept(obj);
WeakReference<T> r = new WeakReference<>(obj);
obj = null;
System.gc();
if(r.get() == null) System.out.println("collected immediately");
else {
System.runFinalization();
System.gc();
if(r.get() == null) System.out.println("collected after finalization");
else System.out.println("still alive");
}
}
which will print
just creating a JFrame
collected immediately
creating and connecting a JFrame
still alive
calling dispose()
collected after finalization
in typical implementations.
It’s worth noting that a frame that was never connected to a display device gets collected immediately like an ordinary object, whereas a frame which was connected and later-on disconnected via a dispose()
call requires a cleanup action, which takes place after the first garbage collector run.
Further, the example uses pack()
rather than setVisible(true)
, to demonstrate that a frame can get connected to a display device without being visible. This makes the difference between isDisplayable()
which reflects whether the component is connected to a screen device and isVisible()
which tells whether the visible property has been set to true
. Finally, a component does only report isShowing()
as true
when it is visible and displayable and all of its parents are showing.