Answer
JUnit’s test lifecycle does not prevent the use of java.lang.ref.WeakReference for leak detection, but you must ensure that no strong references to the object under test survive past the test method. The check should be performed in an @AfterEach (or @After) method after you have nulled all locals, fields, and any callbacks that could retain the instance. Invoking System.gc() in the teardown is permissible as a hint, but it is not guaranteed to trigger collection and can make the test flaky on JVMs that ignore explicit GC calls.
Confirmed Facts
- Each test method receives a fresh test instance unless
@TestInstance(Lifecycle.PER_CLASS) is used.
- A
WeakReference cleared by the garbage collector returns null from get().
- Explicit GC (
System.gc()) is a hint; the JVM may ignore it, especially when started with -XX:+DisableExplicitGC.
- Static fields,
ThreadLocal values, daemon threads, or executor services that retain the object will keep the weak reference non‑null, producing a false positive.
Likely Explanation
The nondeterministic nature of GC means that a passing test only shows that the object was collectable under the test conditions, not that a leak is absent. Conversely, a failing test indicates that something kept a strong reference, which is a reliable signal of a leak (or of test‑setup retention).
Steps for a Portable Leak‑Detection Test
- Declare a
WeakReference<T> weakRef; field in the test class.
- In the test method (or
@BeforeEach): create the object, assign it to a strong local variable, and store weakRef = new WeakReference<>(obj);.
- Execute the code under test.
- Nullify every strong reference you hold: locals, fields, any listener/callback registrations, and set the test instance’s fields to
null if using PER_CLASS.
- In the
@AfterEach method:
- Optionally call
System.gc(); as a hint.
- Wait briefly for the GC to run, e.g., loop with
Thread.yield() or Thread.sleep(10) up to a timeout (e.g., 100 ms) while checking weakRef.get() == null.
- Assert
assertNull(weakRef.get(), "Object was not garbage‑collected – possible leak");
- Ensure the class under test does not retain the instance via static collections,
ThreadLocal, or un‑shut down executors.
Recommendation on System.gc()
Calling System.gc() in @AfterEach is acceptable as a best‑effort hint, but do not rely on it for correctness. If you need deterministic behaviour, replace the explicit call with a short polling loop that checks the weak reference after yielding the thread; this works even when the JVM ignores explicit GC. Only if you observe consistent false negatives should you investigate whether the JVM was launched with -XX:+DisableExplicitGC.
One Missing Diagnostic Detail
Does your test class use @TestInstance(Lifecycle.PER_CLASS) or any static fields that could retain the object after the test method? Knowing this changes whether you need to clear test‑instance fields or static state in the teardown.