There are a few articles out there about how to find memory leaks.
But how about creating one?
I think it will be an interesting exercise so you know what a memory leak looks like in Ruby.
Let’s see some examples.
A Simple Leak
We can create a memory leak by simply adding new objects to an array.
Like this:
a = [] b = {} loop { sleep(1) 10_000.times { a << "abc" } puts GC.stat(b)[:heap_live_slots] }
This creates 10k strings every second & it prints an object count:
285051 295052 305053 315054 325055 335056 345057 355058
The count keeps going up because the GC can't collect these strings, they are being referenced by the containing array (a
).
If a
goes out of scope that will allow the GC to collect all these "abc"
strings. You can test this with the example above by setting a
to nil, then running GC.start
.
You can find a live example here, just click run
to see the results.
C Extension Leak
When you create an object from Ruby the GC keeps track of how much memory it is using, but when using a C extension Ruby has no control over what happens.
If you create a C extension like this:
#include <ruby.h> #include "extconf.h" void *ptr; void Init_extension() { allocate_memory(); } void allocate_memory() { for(int i = 0; i < 10000; i++) { ptr = malloc(1000); } }
The allocate_memory()
function will leak memory because it's using malloc
& it doesn't call free
to release that memory.
As you can see here:
`ps -o rss -p #{$$}`.lines.last # "49036" require './extension' `ps -o rss -p #{$$}`.lines.last # "89512"
This kind of leak won't show up on any heap dump or on GC.stat
, but you will see memory usage grow.
Summary
Now you know what a memory leak looks like, hopefully that will help you find one faster if you ever have this issue. Btw Ruby 2.4.1 has a known memory leak, so you may want to upgrade if you are using this specific version.
Do you have any questions, feedback or an interesting memory leak debugging story? Leave a comment below 🙂
Thanks for reading!
To be more exact “A Simple Leak” is the article ain’t no a memory leak at all. Memory leak happens when “memory which is no longer needed is not released”. In other words, running code loses control over allocated memory. Nothing leaks when the code of “Simple leak” is over or the variable runs out its scope of visibility. So that the example is just a “memory bloat”.
You are right. A real memory leak can only happen with a C extension or a problem at the interpreter level.
But consider this, what if the developer didn’t intend for that array to be kept around in scope?
Maybe you intended to work with an empty array, but somehow you end up working with an array that is on the global scope & it keeps growing. I actually found a case of this with a Ruby gem that was leaking Thread objects & my memory usage kept growing until the gem was fixed.