Objects, Leaks, and try-with-resources
Source: Effective Java, 3rd Edition — Chapter 2 by Joshua Bloch (Items 6–9)
Lesson 1a covered how to obtain instances. This half: do not create junk, do not retain junk, do not finalize junk — and close resources with try-with-resources.
Item 6: Avoid creating unnecessary objects
Reuse when the object is immutable (or you know a mutable one will not change).
String s = new String("bikini"); // wasteful
String s = "bikini"; // one pooled literal
Prefer Boolean.valueOf(...) over the deprecated Boolean(String) constructor — factories may cache.
Expensive objects: compile once
String.matches(regex) compiles a Pattern every call. Cache a static Pattern:
private static final Pattern ROMAN = Pattern.compile("...");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
~6× faster in Bloch’s numbers on short strings. Naming the pattern also clarifies intent. Lazy-init the field only if you measure a real win (Item 83 / 67 — usually not).
Views / adapters
map.keySet() may return the same Set view each time. Creating more is harmless but pointless — they all share the map’s state.
Autoboxing
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i; // ~2^31 Long allocations
Use long sum. Prefer primitives; watch accidental boxing (Item 61).
Object creation is cheap for small objects — clarity wins. Hand-rolled object pools for light objects lose to the GC; pools make sense for heavyweight things like DB connections. Counterweight: Item 50 (defensive copies) — wrong reuse is worse than extra allocation.
Unnecessary objects
1 / 2String.matches(regex) in a hot loop
Pick an answer to see if you are right.
Item 7: Eliminate obsolete references
GC does not save you from unintentional retention. Classic stack:
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size]; // leak: array still holds the object
}
After pop, null the slot:
Object result = elements[--size];
elements[size] = null;
return result;
Classes that manage their own memory (array pools, buffers) must clear freed slots — the GC sees every array cell as a live root.
Do not null every local “just in case.” Prefer narrow scopes (Item 57). Null when you own a long-lived structure.
Other leak sources:
| Source | Mitigation |
|---|---|
| Caches | WeakHashMap if lifetime follows external key refs; else periodic eviction (LinkedHashMap.removeEldestEntry, scheduler) |
| Listeners / callbacks | Deregister, or hold weak refs |
Leaks rarely crash loudly — profilers and code review find them years later.
Starter pop leaves the reference — slotNull is false until you clear it. Tests normalize whitespace so the two printed lines become one check string… wait, normalize collapses whitespace including newlines to spaces. So b,a\ntrue becomes b,a true. Good.
Item 8: Avoid finalizers and cleaners
Not C++ destructors. GC frees memory; non-memory resources need close (Item 9).
Problems:
- No promptness / no guarantee they run — never close files or release DB locks there.
- Behavior varies by GC and JVM — “works on my machine.”
- Finalizers: ignored exceptions, ~50× slower create/destroy, finalizer attacks on non-
finalclasses (malicious subclassfinalizeresurrects a half-built object). Protect with a emptyfinal finalize()or make the class final. - Cleaners (Java 9): better thread control, still unpredictable as a primary cleanup path.
Do this instead: implement AutoCloseable; clients use try-with-resources. Track a closed flag; methods throw IllegalStateException after close.
Legitimate uses: safety net if someone forgets close (still may never run), or reclaiming noncritical native peers. Room example: static nested State (must not reference Room — cycles block GC), register with Cleaner, close() calls cleanable.clean().
Finalizers and cleaners
1 / 2Relying on finalize() to release a database lock promptly
Pick an answer to see if you are right.
Item 9: Prefer try-with-resources
Resources that need close(): streams, JDBC Connection, etc. Finalizer safety nets are weak (Item 8).
try-finally nests badly with two resources and can hide the first exception when close also throws. try-with-resources (Java 7+) needs AutoCloseable:
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
Multiple resources in one try (...). Close failures become suppressed exceptions (Throwable.getSuppressed()). You can still attach catch.
Always prefer this over try-finally for closeable resources.
Finalizers and try-with-resources
1 / 2Closing a file descriptor
Pick an answer to see if you are right.
Starter does nothing — log stays empty until work/close append. try-with-resources guarantees close after work.
Chapter 2 wrap (Items 6–9)
| Item | Rule |
|---|---|
| 6 | Reuse immutables / compiled Patterns; prefer primitives; no DIY light pools |
| 7 | Null obsolete refs in self-managed storage; watch caches and listeners |
| 8 | No finalizers/cleaners for real cleanup — maybe safety net / native peer |
| 9 | try-with-resources for AutoCloseable |
Defensive copying (Item 50) is the opposite instinct when sharing mutable state — covered later.