Introduction
Ruby uses a Generational Garbage Collector based on the hypothesis that most objects die young. Understanding how the GC works is essential for writing memory-efficient Ruby applications and diagnosing performance problems caused by excessive collection pauses.
Key Concepts
- Minor GC: A fast collection that only checks young (newly allocated) objects. Most objects die here.
- Major GC: A slower collection that examines all objects, including those promoted to the old generation.
- Object Promotion: Objects that survive several minor GC cycles are promoted to the old generation, where they are checked less frequently.
- Heap Slots: Fixed-size memory slots where Ruby stores individual objects.
Real World Context
In production Rails applications, GC pauses can add tens of milliseconds to request latency. Knowing whether your app triggers excessive major GCs helps you tune memory settings and reduce tail latencies.
Deep Dive
Ruby's generational GC divides objects into young and old generations. The following code shows how to force a collection and inspect GC statistics:
ruby# Force garbage collection GC.start # Get GC statistics stats = GC.stat puts stats[:count] # Total GC runs puts stats[:minor_gc_count] # Fast young-object collections puts stats[:major_gc_count] # Full collections including old objects puts stats[:heap_live_slots] # Currently occupied heap slots
The GC.stat hash gives you a snapshot of the collector's state. The :count key is the total number of collections, while :minor_gc_count and :major_gc_count break that total down.
Objects survive promotion after a configurable number of minor GC cycles. You can inspect the promotion threshold:
ruby# Objects surviving 3 GC cycles become old GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE] # => 3
This means an object must survive three minor GCs before Ruby considers it long-lived and promotes it.
You can also tune GC behavior through environment variables to match your workload:
bash# Increase initial heap size (reduces early GC frequency) RUBY_GC_HEAP_INIT_SLOTS=600000 # Increase growth factor (larger heap expansions) RUBY_GC_HEAP_GROWTH_FACTOR=1.25 # Set old-malloc limit before triggering a major GC RUBY_GC_OLDMALLOC_LIMIT=64000000
These variables let you trade memory for fewer GC pauses, which is common in memory-rich server environments.
Common Pitfalls
- Over-tuning GC environment variables — Increasing heap size too aggressively wastes memory without improving throughput. Always benchmark before and after changes.
- Calling GC.start in production — Forcing a full GC during request handling introduces unpredictable latency spikes. Let the runtime manage collection timing.
Best Practices
- Monitor GC stats in production — Instrument
GC.statin your metrics pipeline to spot trends in major GC frequency over time. - Use RUBY_GC_HEAP_INIT_SLOTS for known workloads — If your app stabilizes at 500k live objects, pre-allocating avoids dozens of early heap expansions.
Summary
- Ruby uses a generational GC with minor (young-only) and major (full) collections.
- Objects are promoted to the old generation after surviving three minor GCs by default.
GC.statprovides runtime insight into collection counts and heap usage.- Environment variables let you tune heap size and growth to reduce GC frequency.
Code Examples
# Inspect GC state to understand collection behavior
stats = GC.stat
puts "Total GC runs: #{stats[:count]}"
puts "Minor GCs: #{stats[:minor_gc_count]}"
puts "Major GCs: #{stats[:major_gc_count]}"
puts "Live slots: #{stats[:heap_live_slots]}"