githublinkedinemail
← /articles

Understanding Ruby's runtime (not just the language)

Most know the language. Almost none know the runtime. YARV, heap, GC, GVL, inline cache — where engineering lives and where production bugs are born.

4 minruby

Understanding Ruby's runtime (not just the language)

Most Ruby devs know the language.

Almost none know the runtime.

And that's the difference between someone who writes code that works and someone who understands why it works.


Language ≠ Runtime

The language is the syntax.

class User
  def initialize(name)
    @name = name
  end
end

The runtime is everything that happens when that class becomes something executing in memory.

Things like:

  • a method being resolved
  • an object being allocated
  • the GC marking references
  • a thread grabbing the GVL
  • bytecode running on the VM

Language you read. Runtime you feel.

Especially when it stalls.


The Ruby you use is MRI (CRuby)

When you run ruby, you're running MRI — also called CRuby.

It's a runtime written in C that does:

Source (.rb)
   ↓
Parser → AST
   ↓
Compiler → YARV bytecode
   ↓
VM executing bytecode

YARV = Yet Another Ruby VM.

Each of your methods becomes a sequence of bytecode instructions. Something like:

trace        1
putself
putobject    "hello"
opt_send_without_block <calldata!mid:puts>
leave

You never need to look at this.

But when something is slow, that's where the time is being spent.


Ruby's heap

Every Ruby object lives on the heap.

And the heap is divided into pages. Each page contains slots. Each slot holds an object.

Heap
├── Page 0 — [slot][slot][slot][slot]...
├── Page 1 — [slot][slot][slot][slot]...
└── Page 2 — [slot][slot][slot][slot]...

When you do:

user = User.new("Vini")

Ruby:

  1. finds a free slot
  2. creates the object there
  3. returns you a reference

Looks simple. It isn't.

Because the heap isn't infinite.

And when it fills up, someone has to clean.


Enter the GC

Ruby's Garbage Collector is mark and sweep with generational + incremental.

Translation:

  • it marks what's alive
  • frees what isn't
  • prioritizes young objects (which die early)
  • does it in chunks (doesn't stop the world all at once)

And then the classic problem:

"My Ruby is slow."

Could be GC pauses.

Could be fragmentation.

Could be objects living longer than they should (memory bloat).

Without understanding the runtime, you'll never figure it out.


Threads, GVL and the illusion of parallelism

Ruby has threads.

But MRI has the GVL (Global VM Lock).

Only one thread executes Ruby code at a time.

That means:

  • threads help when you're waiting on IO
  • threads do NOT help when you're computing

That's why Ruby web apps use Puma with multiple workers (processes) — because processes have separate GVLs.

We cover this in a dedicated GIL article.


Methods are not functions

At runtime, when you call user.save, Ruby needs to:

  1. look at user's class
  2. walk up the ancestors chain
  3. find the save method
  4. invoke it with the right self

That process is called method lookup.

And it's expensive enough that Ruby has an inline cache inside the VM, remembering where it found the method last time.

If that cache misses (because you define methods dynamically, extend at runtime, etc), performance tanks.

Magic has a cost.


Why this matters in production

You can spend your whole life never understanding the runtime and still make CRUD work.

But the day:

  • the app eats 4GB of memory
  • a thread blocks for 800ms in a GC pause
  • the CPU stays at 100% with no clear reason
  • a dynamic method tanks production performance

…you'll be hands-tied.

Runtime is the terrain.

Language is just what you build on top of it.


The big shift

When you understand the runtime, you stop seeing Ruby as "a cute dynamic language".

And you start seeing it for what it really is:

  • a VM running bytecode
  • a heap being managed
  • a GC fighting allocation
  • threads competing for the GVL
  • caches trying to save your neck

Then debugging stops being superstition.

And performance becomes something you can reason about.


Conclusion

Language is the easy part.

Runtime is where engineering lives.

People who only know the language write code.

People who understand the runtime ship systems that survive production.

And that difference doesn't show up in interviews.

It shows up at 3 AM when the pager goes off.


Understanding Ruby's runtime (not just the language)
Understanding Ruby's runtime (not just the language)