← Back to Blog

Ruby Memory Consumption Analysis: Classes and Methods

Published: June 26, 2025
Tags: ruby, memory, memory-management

Summary

This article analyzes Ruby's memory usage patterns when defining classes and methods, providing insights into memory consumption at the code level.

System Specifications

MacBook Air M2 arm64

Objective

To visualize and understand how much memory is consumed when defining classes and methods in Ruby, helping developers develop a better intuition for memory usage in their code.

Background Knowledge

Heap Area & Memory Allocation

The heap is a region where memory can be dynamically allocated and deallocated during program execution.
Based on C language memory allocation concepts, the memory layout starts from address 0 with reserved areas, followed by the text segment containing machine code instructions. Static data contains constants and static variables. The heap area, as mentioned, allows dynamic memory allocation and deallocation during program execution. The stack area stores local variables, function arguments, and return values, which are stacked when functions start and deallocated when functions end.

Setup

Memory Measurement Code

require "json"
require "objspace"
require "net/http"
require "uri"

# メモリ集計
def snapshot(label, mod)
  meth_memsize = mod.instance_methods(false)
                    .sum { |m| ObjectSpace.memsize_of(mod.instance_method(m)) }
  iseq_memsize = mod.instance_methods(false)
                    .sum { |m| ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(mod.instance_method(m))) }
  iseq_bytes   = mod.instance_methods(false)
                    .sum { |m| RubyVM::InstructionSequence.of(mod.instance_method(m)).to_binary.bytesize }

  puts "== #{label} =="
  puts "  Class memsize   : #{ObjectSpace.memsize_of(mod)} B"
  puts "  Method memsize  : #{meth_memsize} B"
  puts "  ISeq  memsize   : #{iseq_memsize} B"
  puts "  ISeq  bytes     : #{iseq_bytes} B"
  puts
end

# STEP 0: 空クラス
class Demo; end
snapshot("STEP 0 (class only)", Demo)

# STEP 1: 1のみのメソッド
class Demo
  def tiny; 1; end
end
snapshot("STEP 1 (+ tiny)", Demo)

# STEP 2: 通常 FizzBuzz
class Demo
  def fizzbuzz(n = 100)
    (1..n).map { |i|
      if i % 15 == 0 then 'FizzBuzz'
      elsif i %  3 == 0 then 'Fizz'
      elsif i %  5 == 0 then 'Buzz'
      else i end
    }
  end
end
snapshot("STEP 2 (+ fizzbuzz)", Demo)

# STEP 3: 再帰版 FizzBuzz
class Demo
  def fizzbuzz_recursive(n, i = 1, acc = [])
    return acc if i > n
    acc << (i % 15 == 0 ? 'FizzBuzz' :
            i % 3  == 0 ? 'Fizz'     :
            i % 5  == 0 ? 'Buzz'     : i)
    fizzbuzz_recursive(n, i + 1, acc)
  end
end
snapshot("STEP 3 (+ fizzbuzz_recursive)", Demo)

# STEP 4: server.rb へ HTTP リクエストを送るメソッド
class Demo
  def fetch_fizzbuzz_remote(n = 100, host = "127.0.0.1", port = 4567)
    uri  = URI("http://#{host}:#{port}/fizzbuzz?n=#{n}")
    JSON.parse(Net::HTTP.get(uri))
  end
end
# 実際に100件取ってみる
puts "Remote result (first 10) => #{Demo.new.fetch_fizzbuzz_remote(100).first(30)}"
snapshot("STEP 4 (+ fetch_fizzbuzz_remote)", Demo)

# STEP 5: メソッドを2つにする
class Demo
  def tiny; 1; end
  def tiny2; 1; end
end
snapshot("STEP 5 (+ tiny2)", Demo)

# STEP 6: メソッドを3つにする
class Demo
  def tiny; 1; end
  def tiny2; 1; end
  def tiny3; 1; end
end
snapshot("STEP 6 (+ tiny3)", Demo)

# STEP 7: メソッドを10個にする
class Demo
  def tiny; 1; end
  def tiny2; 1; end
  def tiny3; 1; end
  def tiny4; 1; end
  def tiny5; 1; end
  def tiny6; 1; end
  def tiny7; 1; end
  def tiny8; 1; end
  def tiny9; 1; end
  def tiny10; 1; end
end
snapshot("STEP 7 (+ tiny10)", Demo)

FizzBuzz Server

#!/usr/bin/env ruby
# stdlib だけで動く FizzBuzz API サーバ
require 'webrick'   # HTTP サーバ
require 'json'      # JSON 生成

class FizzBuzzServlet < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, res)
    n = (req.query['n'] || 100).to_i
    res['Content-Type'] = 'application/json'
    res.status = 200
    res.body   = JSON.dump(fizzbuzz(n))
  end

  private

  def fizzbuzz(n)
    (1..n).map do |i|
      if    i % 15 == 0 then 'FizzBuzz'
      elsif i %  3 == 0 then 'Fizz'
      elsif i %  5 == 0 then 'Buzz'
      else i end
    end
  end
end

server = WEBrick::HTTPServer.new(Port: 4567, BindAddress: '0.0.0.0')
server.mount '/fizzbuzz', FizzBuzzServlet
trap('INT') { server.shutdown }
server.start

Results Comparison

Output Interpretation

Here's how to read the measurement results:

== STEP N ==
  Class memsize   : Memory area occupied by the class
  Method memsize  : Memory area occupied by methods
  ISeq  memsize   : Ruby instruction sequence memory (local variables, etc.)
  ISeq  bytes     : C-level memory occupation
== STEP 0 (class only) ==
  Class memsize   : 192 B
  Method memsize  : 0 B
  ISeq  memsize   : 0 B
  ISeq  bytes     : 0 B

== STEP 1 (+ tiny) ==
  Class memsize   : 256 B
  Method memsize  : 80 B
  ISeq  memsize   : 456 B
  ISeq  bytes     : 216 B

== STEP 2 (+ fizzbuzz) ==
  Class memsize   : 256 B
  Method memsize  : 160 B
  ISeq  memsize   : 1112 B
  ISeq  bytes     : 932 B

== STEP 3 (+ fizzbuzz_recursive) ==
  Class memsize   : 256 B
  Method memsize  : 240 B
  ISeq  memsize   : 3128 B
  ISeq  bytes     : 1720 B

Remote result (first 10) => [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "Fizz", 22, 23, "Fizz", "Buzz", 26, "Fizz", 28, 29, "FizzBuzz"]
== STEP 4 (+ fetch_fizzbuzz_remote) ==
  Class memsize   : 528 B
  Method memsize  : 320 B
  ISeq  memsize   : 4592 B
  ISeq  bytes     : 2424 B

== STEP 5 (+ tiny2) ==
  Class memsize   : 528 B
  Method memsize  : 400 B
  ISeq  memsize   : 5048 B
  ISeq  bytes     : 2640 B

== STEP 6 (+ tiny3) ==
  Class memsize   : 528 B
  Method memsize  : 480 B
  ISeq  memsize   : 5504 B
  ISeq  bytes     : 2856 B

== STEP 7 (+ tiny10) ==
  Class memsize   : 912 B
  Method memsize  : 1040 B
  ISeq  memsize   : 8696 B
  ISeq  bytes     : 4372 B

Conclusion

The analysis reveals that defining a class in Ruby consumes approximately 256 bytes, with each method adding roughly 80 bytes.
Even meaningless methods contribute to increased memory usage in both class and method areas. Additionally, implementing API call logic significantly increases the class's memory footprint.
Surprisingly, recursive calls consume considerable memory, suggesting that future comparisons with more practical functions would be valuable for understanding real-world memory patterns.