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!