Skip to content
AgileLogics
5 min read

Memory and Garbage Collection

Prerequisites: Frozen objects and mutability

Ruby manages memory for you: you create objects, and the garbage collector (GC) frees the ones nothing refers to any more. You never free memory by hand. But in long-running programs — web servers, background workers — how many objects you create affects both speed and memory use.

How Ruby's GC works, briefly

  • Objects live on Ruby's heap. When it needs space, the GC marks every object still reachable from your program (variables, constants, the objects they refer to...) and sweeps away the rest.
  • The GC is generational: most objects die young, so new objects are checked often and long-lived ones rarely.
  • It's incremental and compacting, which keeps pauses short and memory less fragmented.

Every allocation is work the GC must later do. So the practical skill is allocating less in hot code.

Measuring allocations

def allocations
  before = GC.stat(:total_allocated_objects)
  yield
  GC.stat(:total_allocated_objects) - before
end

words = %w[alpha beta gamma] * 1000

a = allocations { words.map { |w| w.upcase }.select { |w| w.start_with?("A") } }
b = allocations { words.each_with_object([]) { |w, out| out << w.upcase if w.start_with?("a") } }

puts a > b
true

The first version upper-cases every word and builds a full intermediate array; the second only allocates strings for the words it keeps.

Habits that reduce allocations

  • # frozen_string_literal: true — string literals are shared instead of copied each time the line runs.
  • Symbols for internal names and hash keys.
  • Bang methods (map!, strip!, <<) on objects you own, instead of building a new object on each step.
  • Streaming (File.foreach, each_slice, database batches) instead of loading everything into memory at once.

Memory leaks in Ruby

Garbage collection prevents classic leaks, but memory still grows if objects stay reachable: a constant or class-level hash used as a cache that never evicts entries, or an ever-growing array of log lines. When a process's memory climbs steadily, look for collections that only ever grow. Tools such as the memory_profiler gem report where objects are allocated and retained.

Common mistakes

  • Optimizing allocations in code that runs once. Measure first — only hot paths are worth it.
  • Calling GC.start in application code to "fix" memory. It hides the symptom and costs time; find what's keeping objects alive instead.