Логотип Workflow

Article

Memory And Garbage Collector

Topic 6. Memory and Garbage Collector

Memory behavior in Java is a core reliability topic. Many production failures are caused by reference lifetime mistakes, not syntax issues.

Stack and Heap

Basic memory model

stack and heap have different responsibilities:

  • stack stores method frames, local variables, and references,
  • heap stores objects and arrays,
  • method frame disappears after method completion,
  • heap object stays alive while reachable.
int age = 30;          // primitive value
String name = "Alex"; // reference to heap object

How Garbage Collector works

GC uses reachability analysis, not timer-based cleanup.

Simplified flow:

  1. JVM identifies GC Roots (active threads, stack references, static fields, etc.).
  2. Reachable objects are marked alive.
  3. Unreachable objects become reclaim candidates.
  4. Memory may be compacted to reduce fragmentation.

Garbage Collector

Key point: GC timing is nondeterministic. System.gc() is only a hint.

Generational memory model

Most JVM collectors rely on generations:

  • Young Generation: new objects,
  • Old Generation: long-lived objects,
  • Metaspace: class metadata.

Why it helps: many objects are short-lived, so frequent cheap young collections are effective.

GC pauses in practical terms

  • Minor GC: more frequent, typically shorter, mostly young generation.
  • Major/Full GC: less frequent, usually heavier, may involve old generation.

If service latency spikes, long or frequent GC pauses are common suspects.

Typical Java memory leak patterns

A Java memory leak usually means “objects remain reachable accidentally”, not “GC is broken.”

Common causes:

  1. unbounded static collections,
  2. cache without eviction policy,
  3. event listeners/subscriptions never removed,
  4. uncleared ThreadLocal in long-lived threads,
  5. heavyweight object graphs pinned by singleton components.

Example:

public class CacheHolder {
    private static final List<byte[]> CACHE = new ArrayList<>();

    public static void add(byte[] data) {
        CACHE.add(data); // unbounded growth
    }
}

Objects remain reachable through CACHE, so GC cannot reclaim them.

Writing GC-friendly code

  1. bound cache and queue sizes,
  2. release subscriptions/resources explicitly,
  3. avoid unnecessary long-lived references,
  4. reduce temporary allocations in hot loops,
  5. plan lifecycle for large buffers.

Practical diagnostics workflow

  1. Monitor heap usage and GC pause metrics.
  2. Capture heap dump when memory keeps rising.
  3. Use dominator tree to find who retains memory.
  4. Correlate memory growth with workload patterns.

Key takeaway

  • GC removes only unreachable objects.
  • Most memory issues are reference-graph design issues.
  • Memory management quality starts at architecture level.

Quiz

Check what you learned

Please login to pass quizzes.