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!