Module: Heapy::Alive
- Defined in:
- lib/heapy/alive.rb
Overview
This is an experimental module and likely to change. Don’t use in production.
Use at your own risk. APIs are not stable.
What
You can use it to trace objects to see if they are still “alive” in memory. Unlike the heapy CLI this is meant to be used in live running code.
This works by retaining an object’s address in memory, then running GC and taking a heap dump. If the object exists in the heap dump, it is retained. Since we have the whole heap dump we can also do things like find what is retaining your object preventing it from being collected.
Use It
You need to first start tracing objects:
Heapy::Alive.start_object_trace!(heap_file: "./tmp/heap.json")
Next in your code you want to specify the object ato trace
string = "hello world"
Heapy::Alive.trace_without_retain(string)
When the code is done executing you can get a reference to all “tracer” objects by running:
Heapy::Alive.traced_objects.each do |tracer|
puts tracer.raw_json_hash if tracer.object_retained?
end
A few helpful methods on ‘tracer` objects:
-
‘raw_json_hash` returns the hash of the object from the heap dump.
-
‘object_retained?` returns truthy if the object was still present in the heap dump.
-
‘address` a string of the memory address of the object you’re tracing.
-
‘tracked_to_s` a string that represents the object you’re tracing (default
is result of calling inspect on the method). You can pass in a custom representation when initializing the object. Can be useful for when `inspect` on the object you are tracing is too verbose.
-
‘id2ref` returns the original object being traced (if it is still in memory).
-
‘root?` returns false if the tracer isn’t the root object.
See ‘ObjectTracker` for more methods.
If you want to see what retains an object, you can use ‘ObectTracker#retained_by` method (caution this is extremely expensive and requires re-walking the whole heap dump:
Heapy::Alive.traced_objects.each do |tracer|
if tracer.object_retained?
puts "Traced: #{tracer.raw_json_hash}"
tracer.retained_by.each do |retainer|
puts " Retained by: #{retainer.raw_json_hash}"
end
end
end
You can iterate up the whole retained tree by using the ‘retained_by` method on tracers returned. But again it’s expensive. If you have large heap dump or if you’re tracing a bunch of objects, continuously calling ‘retained_by` will take lots of time. We also don’t do any circular dependency detection so if you have two objects that depend on each other, you may hit an infinite loop.
If you know that you’ll need the retained objects of the main objects you’re tracing you can save re-walking the heap the first N times by using the ‘retained_by` flag:
Heapy::Alive.traced_objects(retained_by: true) do |tracer|
# ...
end
This will pre-fetch the first level of “parents” for each object you’re tracing.
Did I mention this is all experimental and may change?
Defined Under Namespace
Classes: ObjectTracker, RootTracker
Class Method Summary collapse
- .address_to_object(address) ⇒ Object
- .retained_by(tracer: nil, address: nil) ⇒ Object
- .start_object_trace!(heap_file: "./tmp/heap.json") ⇒ Object
- .trace_without_retain(object, to_s: nil) ⇒ Object
- .traced_objects(retained_by: false) ⇒ Object
Class Method Details
.address_to_object(address) ⇒ Object
86 87 88 89 90 91 |
# File 'lib/heapy/alive.rb', line 86 def self.address_to_object(address) obj_id = address.to_i(16) / 2 ObjectSpace._id2ref(obj_id) rescue RangeError nil end |
.retained_by(tracer: nil, address: nil) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/heapy/alive.rb', line 107 def self.retained_by(tracer: nil, address: nil) target_address = address || tracer.address tracer = tracer || @retain_hash[address] raise "not a valid address #{target_address}" if target_address.nil? retainer_array = [] Analyzer.new(@heap_file).read do |json_hash| retainers_from_json_hash(json_hash, target_address: target_address, retainer_array: retainer_array) end retainer_array end |
.start_object_trace!(heap_file: "./tmp/heap.json") ⇒ Object
93 94 95 96 97 98 |
# File 'lib/heapy/alive.rb', line 93 def self.start_object_trace!(heap_file: "./tmp/heap.json") @mutex.synchronize do @started ||= true && ObjectSpace.trace_object_allocations_start @heap_file ||= heap_file end end |
.trace_without_retain(object, to_s: nil) ⇒ Object
100 101 102 103 104 105 |
# File 'lib/heapy/alive.rb', line 100 def self.trace_without_retain(object, to_s: nil) tracker = ObjectTracker.new(object_id: object.object_id, to_s: to_s || object.inspect) @mutex.synchronize do @retain_hash[tracker.address] = tracker end end |
.traced_objects(retained_by: false) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/heapy/alive.rb', line 178 def self.traced_objects(retained_by: false) raise "You aren't tracing anything call Heapy::Alive.trace_without_retain first" if @retain_hash.empty? self.gc_start ObjectSpace.dump_all(output: File.open(@heap_file,'w')) retainer_address_array_hash = {} Analyzer.new(@heap_file).read do |json_hash| address = json_hash["address"] tracer = @retain_hash[address] next unless tracer tracer.raw_json_hash = json_hash if retained_by retainers_from_json_hash(json_hash, target_address: address, retainer_array: tracer.retained_by) end end @retain_hash.values end |