Java Advanced · Месяц 1
Garbage Collection & Memory
Goal: understand garbage collection, find memory leaks and use profiling tools. Duration: 120 min
1. Why GC?
In C you free() memory manually: forgetting it leaks memory, freeing twice crashes. Java's garbage collector frees objects that are no longer reachable.
An object is reachable if there is a path from a GC root: local variables on stacks, static fields, active threads, JNI references.
2. Generational hypothesis
Most objects die young.
Heap
├── Young generation
│ ├── Eden (new objects)
│ └── Survivor S0/S1
└── Old generation (long-lived objects)
- Minor GC: cleans young gen often and fast.
- Objects surviving several minor GCs are promoted to old gen.
- Major/Full GC: cleans old gen: slower.
3. Garbage collectors
| GC | Flag | Good for |
|---|---|---|
| Serial | -XX:+UseSerialGC |
tiny apps, 1 CPU |
| Parallel | -XX:+UseParallelGC |
batch throughput |
| G1 (default) | -XX:+UseG1GC |
most server apps |
| ZGC | -XX:+UseZGC |
very low pauses (< 1 ms), large heaps |
| Shenandoah | -XX:+UseShenandoahGC |
low pause (Red Hat builds) |
Metrics: throughput (time not in GC) vs latency (pause length) vs footprint (memory).
4. GC logs
java -Xlog:gc*:file=gc.log:time,uptime -Xmx512m -jar app.jar
[2.345s] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 245M->38M(512M) 6.712ms
Before → after (heap size), pause time.
5. Memory leaks in Java
Java can still leak: objects stay reachable but are never used again.
Typical causes:
// 1. Growing static collections
public class Cache {
private static final Map<String, byte[]> CACHE = new HashMap<>(); // never evicted
}
// 2. Listeners never removed
eventBus.register(listener); // no unregister
// 3. ThreadLocal in thread pools without remove()
private static final ThreadLocal<User> CURRENT = new ThreadLocal<>();
// 4. Keys with broken equals/hashCode in HashMap (duplicates accumulate)
// 5. Unclosed resources (streams, connections)
6. Profiling tools
| Tool | Use |
|---|---|
jps |
list Java processes |
jcmd <pid> GC.heap_info |
heap summary |
jcmd <pid> GC.heap_dump heap.hprof |
heap dump |
jstat -gc <pid> 1000 |
GC stats every second |
| VisualVM | live CPU/memory, heap dumps |
| Java Flight Recorder (JFR) + JDK Mission Control | production-safe profiling |
| Eclipse MAT | analyse heap dumps: "dominator tree", leak suspects |
java -XX:StartFlightRecording=duration=60s,filename=rec.jfr -jar app.jar
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -jar app.jar
7. Hands-on: create and find a leak
public class LeakDemo {
private static final List<byte[]> LEAK = new ArrayList<>();
public static void main(String[] args) throws Exception {
while (true) {
LEAK.add(new byte[1024 * 1024]); // 1 MB per iteration, never released
Thread.sleep(50);
}
}
}
- Run with
-Xmx256m -XX:+HeapDumpOnOutOfMemoryError. - Watch in VisualVM: heap grows and GC can't free it.
- Open the dump in MAT/VisualVM: find
LEAKholding the arrays. - Fix: bounded cache (e.g.
LinkedHashMapwithremoveEldestEntry, or Caffeine).
8. References types (bonus)
| Type | Collected when |
|---|---|
| Strong | never while reachable |
SoftReference |
memory is low (caches) |
WeakReference |
at next GC (WeakHashMap) |
PhantomReference |
cleanup tracking |
9. Interview corner
- How does GC decide an object is garbage?
- What is the difference between minor and full GC?
- Can Java have memory leaks? Give an example.
- Which GC would you choose for a payment API with strict latency? (ZGC/G1 with tuned pause goals)
10. Homework
Profile one of your previous projects with JFR: record 60 s, open in JDK Mission Control, and write down the top 3 allocation hotspots and GC pause stats.