oj: Memory leak in Oj.dump

Ruby version: ruby 2.4.4p296 (2018-03-28 revision 63013) [x86_64-darwin17] Oj version: oj (3.6.8, 2.18.5)

๐Ÿ‘‹ Iโ€™ve observed some leaky behaviour in my application when using Oj to dump JSON. Itโ€™s more visible when the object(hash/array) being dumped contains symbols. Oj keeps references to all symbols that are dumped so they canโ€™t GCโ€™d

Hereโ€™s an example:

require 'objspace'
require 'oj'
require 'json'
require 'get_process_mem'

def record_allocation
  GC.start
  GC.start

  mem = GetProcessMem.new
  puts "Before - Process Memory: #{mem.mb} mb"
  puts "Before - Objects count: #{ObjectSpace.each_object.count}"
  puts "Before - Symbols count: #{Symbol.all_symbols.size}"

  yield

  GC.start
  GC.start
  GC.start

  puts "After - Process Memory: #{mem.mb} mb"
  puts "After - Objects count: #{ObjectSpace.each_object.count}"
  puts "After - Symbols count: #{Symbol.all_symbols.size}"
end

record_allocation do
  data = 1_000_000.times.map { |i| "string_number_#{i}".to_sym } # array of symbols
  Oj.dump(data)
end

output

$ RBENV_VERSION=2.4.4 time ruby -v -W0 test.rb
ruby 2.4.4p296 (2018-03-28 revision 63013) [x86_64-darwin17]
Before - Process Memory: 12.703125 mb
Before - Objects count: 16070
Before - Symbols count: 3496
After - Process Memory: 308.328125 mb
After - Objects count: 2016040
After - Symbols count: 1003496
        3.97 real         3.58 user         0.32 sys

We can see that a alot of symbols are retained after the block.

This is the same test but using ruby JSON and we can see that no symbols or extra objects are retained.

record_allocation do
  data = 1_000_000.times.map { |i| "string_number_#{i}".to_sym } # array of symbols
  JSON.dump(data)
end

output

$ RBENV_VERSION=2.4.4 time ruby -v -W0 test.rb
ruby 2.4.4p296 (2018-03-28 revision 63013) [x86_64-darwin17]
Before - Process Memory: 13.1171875 mb
Before - Objects count: 16070
Before - Symbols count: 3496
After - Process Memory: 339.734375 mb
After - Objects count: 16042
After - Symbols count: 3496
        5.30 real         4.88 user         0.37 sys

Let me know if you need help reproducing this. Thanks

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 15 (9 by maintainers)

Most upvoted comments

I can confirm that it is fixed for me now ๐ŸŽ‰

There was a bug in my test which was causing the extra objects to be retained in data. Iโ€™ve updated it below

require 'objspace'
require 'oj'
require 'json'
require 'get_process_mem'

def record_allocation
  GC.start
  GC.start

  mem = GetProcessMem.new
  puts "Oj: #{Oj::VERSION}"
  puts "Before - Process Memory: #{mem.mb} mb"
  puts "Before - Objects count: #{ObjectSpace.each_object.count}"
  puts "Before - Symbols count: #{Symbol.all_symbols.size}"

  yield

  GC.start
  GC.start
  GC.start

  puts "After - Process Memory: #{mem.mb} mb"
  puts "After - Objects count: #{ObjectSpace.each_object.count}"
  puts "After - Symbols count: #{Symbol.all_symbols.size}"
end

def dump
  data = 1_000_000.times.map { |i| "string_number_#{i}".to_sym } # array of symbols
  Oj.dump(data)
end

record_allocation do
  dump
end

Before:

$ RBENV_VERSION=2.4.4 gem install oj -v 3.6.8
Fetching: oj-3.6.8.gem (100%)
Building native extensions.  This could take a while...
Successfully installed oj-3.6.8
1 gem installed

$ RBENV_VERSION=2.4.4 ruby -v -W0 test.rb
ruby 2.4.4p296 (2018-03-28 revision 63013) [x86_64-darwin17]
Oj: 3.6.8
Before - Process Memory: 12.6328125 mb
Before - Objects count: 16074
Before - Symbols count: 3496
After - Process Memory: 308.13671875 mb
After - Objects count: 2016043
After - Symbols count: 1003496

After:

$ RBENV_VERSION=2.4.4 gem install oj -v 3.6.10
Fetching: oj-3.6.10.gem (100%)
Building native extensions.  This could take a while...
Successfully installed oj-3.6.10
1 gem installed

$ RBENV_VERSION=2.4.4 ruby -v -W0 test.rb
ruby 2.4.4p296 (2018-03-28 revision 63013) [x86_64-darwin17]
Oj: 3.6.10
Before - Process Memory: 13.140625 mb
Before - Objects count: 16074
Before - Symbols count: 3496
After - Process Memory: 304.25 mb
After - Objects count: 16043
After - Symbols count: 3496

Thanks @ohler55 ๐Ÿ‘