Ruby Logo

Garbage Collection & Memory Management

Understand Ruby's automatic memory management through garbage collection. Learn about mark-and-sweep algorithms, object lifecycle, and GC statistics.

Home Ruby Garbage Collection & Memory Management

Ruby Garbage Collection Mastery

Understand Ruby's automatic memory management through garbage collection. Learn about mark-and-sweep algorithms, object lifecycle, GC statistics, and how to optimize memory usage in your applications.

Understanding Garbage Collection

Garbage Collection (GC): Ruby's automatic memory management system that reclaims memory from objects that are no longer reachable or needed by your program. This prevents memory leaks and keeps your application running efficiently.

Real-World Analogy

Think of GC like a smart cleaning service: Just as a cleaning service automatically removes trash from your office when bins are full, Ruby's GC automatically removes unused objects from memory when space is needed. It knows which objects are still being used (reachable) and which can be safely removed (unreachable).

Object Lifecycle

# Object lifecycle demonstration
def
demonstrate_object_lifecycle
# 1. Object creation - allocates memory
large_array = Array.new(100_000) { |i| "Item #{i}" }
puts "Created large array with #{large_array.size} items"

# 2. Object usage - object is reachable
puts "First item: #{large_array.first}"
puts "Last item: #{large_array.last}"

# 3. Object becomes unreachable when method ends
# large_array goes out of scope
# GC will eventually reclaim this memory
end

# Check memory before
before_objects = GC.stat[:heap_live_slots]
puts "Objects before: #{before_objects}"

demonstrate_object_lifecycle

# Force garbage collection
GC.start

after_objects = GC.stat[:heap_live_slots]
puts "Objects after GC: #{after_objects}"
puts "Objects collected: #{before_objects - after_objects}"

Mark-and-Sweep Algorithm

Mark-and-Sweep: Ruby's GC algorithm works in two phases: Mark (identify reachable objects) and Sweep (reclaim memory from unreachable objects). This ensures only truly unused objects are collected.

GC Process Phases

📍 Mark Phase
  • Start from "root" objects (global variables, stack variables)
  • Follow all object references recursively
  • Mark every reachable object as "live"
  • Objects not marked are considered "garbage"
🧹 Sweep Phase
  • Scan through all allocated objects
  • Free memory from unmarked objects
  • Add freed memory back to available pool
  • Reset marks for next GC cycle
# Demonstrating object reachability
def
create_objects
# These objects will be reachable
@instance_var = "I'm reachable via instance variable"
@@class_var = "I'm reachable via class variable"
$global_var = "I'm reachable via global variable"

# This object will become unreachable
local_var = "I'll be garbage collected"
temp_array = Array.new(1000) { |i| "temp #{i}" }

# Return something to keep it reachable
"Method finished"
# local_var and temp_array become unreachable here
end

# Object references and reachability
obj1 = "Hello"
obj2 = obj1 # obj1 is reachable via obj2
obj1 = nil # obj1 is nil, but string is still reachable via obj2
obj2 = nil # Now the string "Hello" is unreachable

# Circular references are handled correctly
array1 = []
array2 = []
array1 << array2
array2 << array1 # Circular reference
array1 = nil
array2 = nil # Both arrays become unreachable despite circular ref

GC Statistics & Monitoring

GC.stat: Ruby provides detailed statistics about garbage collection performance, memory usage, and collection frequency. These metrics are essential for performance optimization.

Key GC Statistics

# Get comprehensive GC statistics
gc_stats = GC.stat

# Key metrics to monitor
puts "Live objects: #{gc_stats[:heap_live_slots]}"
puts "Free slots: #{gc_stats[:heap_free_slots]}"
puts "Total slots: #{gc_stats[:heap_available_slots]}"
puts "GC count: #{gc_stats[:count]}"
puts "Major GC count: #{gc_stats[:major_gc_count]}"
puts "Minor GC count: #{gc_stats[:minor_gc_count]}"

# Memory usage
puts "Heap pages: #{gc_stats[:heap_allocated_pages]}"
puts "Heap length: #{gc_stats[:heap_length]}"
puts "Total allocated objects: #{gc_stats[:total_allocated_objects]}"
puts "Total freed objects: #{gc_stats[:total_freed_objects]}"

# Calculate memory efficiency
live_objects = gc_stats[:heap_live_slots]
total_slots = gc_stats[:heap_available_slots]
efficiency = (live_objects.to_f / total_slots * 100).round(2)
puts "Heap efficiency: #{efficiency}%"

GC Generations

# Ruby uses generational GC
# Young objects are collected more frequently
# Old objects are collected less frequently

def
demonstrate_generations
# Create many short-lived objects
1000.times do |i|
temp = "Temporary string #{i}"
temp.upcase # Use it briefly
# temp becomes unreachable at end of iteration
end

# Create long-lived object
@persistent = Array.new(100) { |i| "Persistent #{i}" }
end

before_minor = GC.stat[:minor_gc_count]
before_major = GC.stat[:major_gc_count]

demonstrate_generations

after_minor = GC.stat[:minor_gc_count]
after_major = GC.stat[:major_gc_count]

puts "Minor GCs triggered: #{after_minor - before_minor}"
puts "Major GCs triggered: #{after_major - before_major}"

GC Control & Optimization

GC Control: While Ruby's GC is automatic, you can control when it runs, disable it temporarily, and tune its behavior for specific performance requirements.

Manual GC Control

# Manual garbage collection
GC.start
# Force immediate garbage collection

# Disable GC temporarily (dangerous!)
GC.disable
puts "GC disabled: #{GC.disable?}"

# Create objects while GC is disabled
1000.times { |i| "String #{i}" }

# Re-enable GC
GC.enable
puts "GC enabled: #{!GC.disable?}"

# Check if GC is needed
puts "GC stress mode: #{GC.stress}"

# Enable stress mode for testing (runs GC after every allocation)
GC.stress = true
Array.new(10) { |i| "Stress test #{i}" }
GC.stress = false

Memory Profiling

# Memory profiling utilities
def
memory_usage
stats = GC.stat
live_objects = stats[:heap_live_slots]
total_objects = stats[:total_allocated_objects]
freed_objects = stats[:total_freed_objects]

{
live_objects: live_objects,
total_allocated: total_objects,
total_freed: freed_objects,
gc_count: stats[:count],
heap_pages: stats[:heap_allocated_pages]
}
end

# Benchmark memory usage
def
benchmark_memory
(&block)
before = memory_usage
GC.start # Clean slate
before = memory_usage

result = block.call

after = memory_usage
GC.start # Force collection
after_gc = memory_usage

puts "Before: #{before[:live_objects]} live objects"
puts "After: #{after[:live_objects]} live objects"
puts "After GC: #{after_gc[:live_objects]} live objects"
puts "Objects created: #{after[:live_objects] - before[:live_objects]}"
puts "Objects collected: #{after[:live_objects] - after_gc[:live_objects]}"

result
end

# Example usage
result = benchmark_memory do
Array.new(10_000) { |i| "String #{i}" }
end

What You've Learned

Key Takeaways

  • Automatic memory management: Ruby's GC automatically reclaims memory from unreachable objects
  • Mark-and-sweep algorithm: Two-phase process that identifies live objects and reclaims dead ones
  • Generational collection: Young objects are collected more frequently than old objects
  • Circular references are handled: Ruby's GC can collect objects with circular references
  • GC monitoring and control: Use GC.stat for monitoring and GC.start for manual collection

Try It Yourself - Interactive Practice

Learning Tip: Understanding garbage collection helps you write more memory-efficient Ruby code. Experiment with these examples to see GC in action!

Interactive Code Runner

Ruby Code Editor
Output will appear here when you run the code...

Quick Navigation

Related Topics

Video Tutorial

Watch and learn garbage collection & memory management

Pro Tip: After reading through the content above, watch this video to reinforce your understanding and see the concepts in action!