Why does Ruby release memory only sometimes?

2019-02-18 06:34发布

问题:

Depending on how I generate a string Ruby will release the memory to the OS or it won't. The first test code will take up about 235MB

size = 2**22
string = '!@#$%^&*()-+~`a1234567890abcdefghijklmnopqrstuvwxyz' * size
puts 'Sleeping...'
sleep(5)
string = nil
GC.start
puts 'Just sitting here..'
gets.chomp

After GC.start is called, memory used by the test will shrink back to a few kilobytes. But if I run the same test with string = (0...size).map { (65 + rand(26)).chr }.join, memory will shoot up to 250MB and memory use will actually increase to 290MB after calling GC.start.

EDIT: I'm using Ruby 1.9.3-p448 as the project I'm working on requires it. Although I'll test it on Ruby 2.2 and come back with results.

EDIT 2: Running the test code in Ruby 2.1 (Ruby 2.2 wasn't available in RVM and I just wanted to run the test quickly) gave similar results. Memory still didn't decrease to a reasonable state. It went from 234MB BGCs (before GC.start) to 197MB AGCs. Note: the memory sizes were different because I ran it on a different machine but the specific sizes don't matter just the relative increases and decreases (or non-decreases).

回答1:

Ruby MRI does not release memory back to the OS.

Here's what I see with Ruby MRI 2.2 on OSX 10.10, using typical ps -o rss:

  • Allocating the big string by using * uses ~220MB.

  • Allocating the big string by using map uses ~340MB.

On my system, the GC.start doesn't do anything to the RSS. In other words, I see the RAM usage stay the same.

Notably, the map is using a lot of RAM:

  • (0...size).map{ '' } uses ~300MB.

When I loop your examples, something interesting emerges:

  • Allocating the big string by using * continues to use the same RAM, i.e. RSS doesn't change much.

  • Allocating the big string by using map grows by ~40M per loop.

  • Doing just (0...size).map{ '' } grows by ~40M per loop.

This shows me that Ruby map may have a RAM-related issue. It's not exactly a problem, because Ruby isn't raising NoMemoryException, but does seem to be a non-optimal use of RAM.