Skip to content

Instantly share code, notes, and snippets.

@tdg5
Last active October 7, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tdg5/0b9f145edb5114a2dca1 to your computer and use it in GitHub Desktop.
Save tdg5/0b9f145edb5114a2dca1 to your computer and use it in GitHub Desktop.
Example seeming to demonstrate that unary ampersand operator holds on to obj references in Ruby 2.1.x
# Create some special classes to facilitate tracking allocated objects.
class TrackedArray < Array; end
class TrackedString < String; end
STRANG = "a" * 5000
class ClingyObjects
def generate(should_cling = false)
strs = TrackedArray.new
30000.times { strs << TrackedString.new(STRANG) }
char_count = 0
# I'm not sure why, but using the unary & operator on the Array, whether
# through #each or #map, prevents the allocated objects from being GC'd.
# Maybe I'm missing something, but after this method returns nothing
# should refer to the strs Array or any of the objects contained in the
# Array, so GC should proceed without issue. What gives?
strs.each(&:length) if should_cling
strs.each {|x| char_count += x.length }
char_count
end
# Helper to print object allocation stats.
def object_stats(tag)
puts "#{tag}:"
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}"
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}"
end
def print_with_stats(char_count)
object_stats("Before GC")
# Run the garbage collector.
GC.start
object_stats("After GC")
puts char_count
end
end
def wrapper
clinger = ClingyObjects.new
puts "Non-clingy:"
count = clinger.generate
clinger.print_with_stats(count)
puts "\nClingy:"
count = clinger.generate(:should_cling)
clinger.print_with_stats(count)
# Try to GC again for fun
puts "\nTry GC again"
GC.start
clinger.print_with_stats(count)
puts "\nDitch clinger and try GC again"
clinger = nil
5.times do
GC.start
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}"
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}"
puts "\nSleep a bit and try again"
sleep 3
end
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}"
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}"
end
wrapper
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}"
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}"
# Non-clingy:
# Before GC:
# TrackedArray: 1
# TrackedString: 30000
# After GC:
# TrackedArray: 0
# TrackedString: 0
# 150000000
# Clingy:
# Before GC:
# TrackedArray: 1
# TrackedString: 30000
# After GC:
# TrackedArray: 1
# TrackedString: 30000
# 150000000
# Try GC again
# Before GC:
# TrackedArray: 1
# TrackedString: 30000
# After GC:
# TrackedArray: 1
# TrackedString: 30000
# 150000000
# Ditch clinger and try GC again
# TrackedArray: 1
# TrackedString: 30000
# Sleep a bit and try again
# TrackedArray: 1
# TrackedString: 30000
# Sleep a bit and try again
# TrackedArray: 1
# TrackedString: 30000
# Sleep a bit and try again
# TrackedArray: 1
# TrackedString: 30000
# Sleep a bit and try again
# TrackedArray: 1
# TrackedString: 30000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment