ruby/ruby on rails memory leak detection
Some tips to find memory leaks in Rails:
- use the Bleak House plugin
- implement Scout monitoring specifically the memory usage profiler
- try another simple memory usage logger
The first is a graphical exploration of memory usage by objects in the ObjectSpace.
The last two will help you identify specific usage patterns that are inflating memory usage, and you can work from there.
As for specific coding-patterns, from experience you have to watch anything that's dealing with file io, image processing, working with massive strings and the like.
I would check whether you are using the most appropriate XML library - ReXML is known to be slow and believed to be leaky (I have no proof of that!). Also check whether you can memoize expensive operations.
Want to determine whether we have memory leak in ROR
Because of garbage collection, ruby will never have the kind of memory leak that occurs in C/C++ where the program was supposed to free the memory it no longer needs but did not.
What can happen is runaway memory because you are holding on to references which you don't need. Typically this happens when you keep things in class instance collections but don't cull the list as things are unneeded or old.
Another thing which can happen is an interaction between ruby memory management and the OS memory allocator. There is a very good article, What causes Ruby memory bloat by Hongli Lai on this. This might be something you can do very little about since it is not a memory "leak" in your code.
A feature was added in ruby 2.7 which addresses the issue in Hongli Lai's article. The method is GC.compact
which is not called automatically but will defragment the ruby heap.
How do I track down a memory leak in my Ruby code?
I did not find ruby-prof very useful when it came to locating memory leaks, because you need a patched Ruby interpreter. Tracking object allocation has become easier in Ruby 2.1. Maybe it is the best choice to explore this yourself.
I recommend the blog post Ruby 2.1: objspace.so by tmml who is one of the Ruby core developers. Basically you can fetch a lot of information while debugging your application:
ObjectSpace.each_object{ |o| ... }
ObjectSpace.count_objects #=> {:TOTAL=>55298, :FREE=>10289, :T_OBJECT=>3371, ...}
require 'objspace'
ObjectSpace.memsize_of(o) #=> 0 /* additional bytes allocated by object */
ObjectSpace.count_tdata_objects #=> {Encoding=>100, Time=>87, RubyVM::Env=>17, ...}
ObjectSpace.count_nodes #=> {:NODE_SCOPE=>2, :NODE_BLOCK=>688, :NODE_IF=>9, ...}
ObjectSpace.reachable_objects_from(o) #=> [referenced, objects, ...]
ObjectSpace.reachable_objects_from_root #=> {"symbols"=>..., "global_tbl"=>...} /* in 2.1 */
With Ruby 2.1 you can even start to track allocation of new objects and gather metadata about every new object:
require 'objspace'
ObjectSpace.trace_object_allocations_start
class MyApp
def perform
"foobar"
end
end
o = MyApp.new.perform
ObjectSpace.allocation_sourcefile(o) #=> "example.rb"
ObjectSpace.allocation_sourceline(o) #=> 6
ObjectSpace.allocation_generation(o) #=> 1
ObjectSpace.allocation_class_path(o) #=> "MyApp"
ObjectSpace.allocation_method_id(o) #=> :perform
Use pry and pry-byebug and start exploring the memory heap where you think it will probably grow, respectively try different segments in your code. Before Ruby 2.1 I always relied on ObjectSpace.count_objects
and calculated the result's difference, to see if one object type grows in particularly.
The garbage collection works properly when the number of objects growing are retested back to a much smaller amount during the iterations as opposed to keep growing. The garbage collector should run all the time anyway, you can reassure yourself by looking into the Garbage Collector statistics.
From my experience this is either String or Symbol (T_STRING
). Symbols before ruby 2.2.0 were not garbage collected so make sure your CSV or parts of it is not converted into symbols on the way.
If you do not feel comfortable, try to run your code on the JVM with JRuby. At least the memory profiling is a lot better supported with tools like VisualVM.
Finding the cause of a memory leak in Ruby
It looks like you are entering The Lost World here. I don’t think the problem is with c-bindings in racc
either.
Ruby memory management is both elegant and cumbersome. It stores objects (named RVALUE
s) in so-called heaps of size of approx 16KB. On a low level, RVALUE
is a c-struct, containing a union
of different standard ruby object representations.
So, heaps store RVALUE
objects, which size is not more than 40 bytes. For such objects as String
, Array
, Hash
etc. this means that small objects can fit in the heap, but as soon as they reach a threshold, an extra memory outside of the Ruby heaps will be allocated.
This extra memory is flexible; is will be freed as soon as an object became GC’ed. That’s why your testcase with big_string
shows the memory up-down behaviour:
def report
puts 'Memory ' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`
.strip.split.map(&:to_i)[1].to_s + 'KB'
end
report
big_var = " " * 10000000
report
big_var = nil
report
ObjectSpace.garbage_collect
sleep 1
report
# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 11788KB
But the heaps (see GC[:heap_length]
) themselves are not released back to OS, once acquired. Look, I’ll make a humdrum change to your testcase:
- big_var = " " * 10000000
+ big_var = 1_000_000.times.map(&:to_s)
And, voilá:
# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 57448KB
The memory is not released back to OS anymore, because each element of the array I introduced suits the RVALUE
size and is stored in the ruby heap.
If you’ll examine the output of GC.stat
after the GC was run, you’ll find that GC[:heap_used]
value is decreased as expected. Ruby now has a lot of empty heaps, ready.
The summing up: I don’t think, the c
code leaks. I think the problem is within base64 representation of huge image in your css
. I have no clue, what’s happening inside parser, but it looks like the huge string forces the ruby heap count to increase.
Hope it helps.
How can I track down a memory leak in a rails app?
I managed to figure it out. I was instantiating runtime classes on each iteration which apparently do not get GC'd. Refactoring to not use Class.new
fixed the problem.
So if anyone's googling this, Class.new
creates memory leaks.
Find memory leaks in a Rails application
I'd try using passenger (which automatically restarts and manages rails instances that grow too large in memory - much easier than rebooting mongrels that have strayed off sane memory constraints). Also you might have luck with ruby enterprise edition fork of 1.8.7 which backports some memory management fixes from 1.9 (like allowing the VM to shrink when it's using less memory) - that change might have worked it's way back to normal 1.8.7 though, although I am not sure. The claim with REE is that you can reduce memory consumption with 33% for rails applications.
Ruby stuff generally tend to grow over time and need rebooting, with passenger it does that automatically for you. It's worked perfectly for me so I can really recommend it.
http://www.modrails.com/
It also has good memory analytic functions
http://www.modrails.com/documentation/Users%20guide%20Apache.html#_analysis_and_system_maintenance
Related Topics
Redirect_To Using Post in Rails
Best Practices With Stdin in Ruby
What Is Ruby'S Double-Colon '::'
How to Update Ruby Gems from Behind a Proxy (Isa-Ntlm)
Ruby Class Instance Variable Vs. Class Variable
Ruby: String Comparison Issues
What Does the (Unary) * Operator Do in This Ruby Code
Rvm Installation Not Working: "Rvm Is Not a Function"
What's the Difference Between Equal, Eql, ===, and ==
How to Track System-Specific Config Files in a Repo/Project
Look Up All Descendants of a Class in Ruby
How to Generate a Random String in Ruby
Combining Implicit Wait and Explicit Wait Together Results in Unexpected Wait Times
How to Convert a Bigdecimal to a 2-Decimal-Place String