Memory leak in Ruby 1.8.6 String class

So I ran into this memory leak about a year ago but had forgotten about it so I’m going to document it in the hopes I check my own blog next time I find it. I came across the leak again when looking into why one of my apps was getting so bloated after running for a few days. The leak has been reported in numerous places but doesn’t appear to have been patched in 1.8.6.

This should demonstrate it:

# Just a helper method to show the memory usage output
# @NOTE: Won't work on Windows
def log
  leak='fix'
  ps = %x(ps u -p #{Process.pid}).strip.split(/\n/).last.split(/\s+/)
  puts "#{ps[4]}     #{ps[5]}"
end
 
# This leaks memory
def bad
  "ruby+memory+leak".split('+')
end
 
# Defining a variable before the String#split
# fixes the leak
def good
  rm = '+'
  "ruby+memory+leak".split(rm)
end
 
 
puts "VSZ       RSS"
500_000.times do |i|
  good
  log if i%100000 == 0 
end
 
puts "\nWatch me leak!"
500_000.times do |i|
  bad
  log if i%100000 == 0 
end

So the moral of the story is, make sure to define a variable in methods that use String#split, String#gsub and the like. This doesn’t leak in ruby 1.8.4. I haven’t checked 1.9, but I’ve heard it’s fixed there too.
Here is a more complete script demonstrating the issue with both class and instance methods (since some reporters have mentioned it being strictly a class method problem).