Reachability explains retention
Objects generally occupy managed heap memory; each thread has execution frames, although runtime optimization can eliminate some allocations. Garbage collection reclaims unreachable objects rather than objects the program no longer intends to use. A long-lived map, listener or thread-local value can retain a large graph. Classify retained memory by ownership and lifetime instead of calling every high heap measurement a leak.
Resources need deterministic lifetimes
Database connections, files and sockets are not safely managed by waiting for garbage collection. Use try-with-resources for AutoCloseable ownership and respect pool/library return semantics. Closing one layer may close another underlying resource, so define who owns the stream. Exception handling should preserve the primary failure and allow suppressed close failures to remain available for diagnosis.
Investigate the request path
Begin with a symptom and evidence: latency percentiles, error rates, CPU, allocation, thread states and dependency spans. High CPU suggests different hypotheses from parked workers or a saturated connection pool. Use production-appropriate profiling or recordings, compare representative before/after load, and make one interpretable change at a time. A microbenchmark that avoids network, warmup or allocation can answer a useful narrow question without predicting whole-service latency.
Worked Java example
Illustrative snippet: imports, surrounding types or collaborators may be omitted. Read the assumptions before adapting it.
static String firstLine(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
return reader.readLine();
}
}Walkthrough
The reader is created at the try boundary and closed whether reading succeeds or throws. The caller receives the first line or null for an empty file and handles the declared IOException. The method does not return the reader after closing it. Character decoding, line length and the file system can dominate cost even though only one line is requested.
Practice it yourself
A service has low CPU but rising request latency and many threads waiting for connections. What would you examine before adding threads?
Check your reasoning
Inspect pool occupancy/wait times, slow queries, leaked/unreturned connections, transaction duration and downstream limits. More request threads may add waiting rather than throughput. Verify the bottleneck with traces and database evidence before changing pool settings.
Recall checklist
- Explain the main invariant or contract without looking.
- Trace the example and test an edge case.
- State time, space, input variables and assumptions where relevant.
- Explain one alternative and when you would choose it.