Skip to content

Memory leaks due to overwritten local variables being wrongly kept alive on the stack #4439

@ivoanjo

Description

@ivoanjo

Environment

Running jruby 9.1.7.0 (2.3.1) 2017-01-11 68056ae Java HotSpot(TM) 64-Bit Server VM 25.111-b14 on 1.8.0_111-b14 +jit [linux-x86_64] on Linux maruchan 4.9.0-11-generic #12-Ubuntu SMP Mon Dec 12 16:18:23 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux.

Expected Behavior

Example code:

class Dummy
  def initialize
    @payload = Array.new(100000)
  end

  def next
    @next = Dummy.new
  end
end

class Test
  def self.test
    dummy = Dummy.new
    iterations = 0

    while true
      dummy = dummy.next
      iterations += 1

      puts "ran iteration #{iterations}"

      GC.start if iterations % 10 == 0
      sleep if iterations == 200 && ENV['PAUSE_AFTER_ITERATIONS'] == '1'
    end
  end
end

Test.test

On MRI this code seems to behave correctly (e.g. it does not leak memory) and I can get up to hundreds of thousands of iterations with < 16MB memory being used.

Actual Behavior

Running with jruby:

$ jruby -J-Xmx200m leak_testcase.rb
ran iteration 1
ran iteration 2
...
ran iteration 466
ran iteration 467
Error: Your application used more memory than the safety cap of 200M.
Specify -J-Xmx####M to increase it (#### = cap size in MB).
Specify -w for full java.lang.OutOfMemoryError: Java heap space stack trace

By pausing after a few iterations and looking at a memory dump (with PAUSE_AFTER_ITERATIONS=1 jruby -J-Xmx200m leak_testcase.rb), we can see the following:

memory-dump-instances

E.g. all instances of Dummy are still alive, whereas looking at the code we are creating a linked list but we never keep references to previous nodes so we should only have a single instance visible in memory.

Looking at one of the instances, we see that they are being kept alive due to a reference stored on the stack:

references

and by asking for a list of stack references we see that there are two Dummy instances being referred to from the stack, not one as expected.

stack-references

It seems like the code generating is leaving the first dummy instance referenced on the stack and thus causing the memory leak as the whole list will be kept in memory, rather than just the last element.

We (@Talkdesk) hit this in production as we were iterating a large collection using hyperclient and thus were hitting memory limits even though we were doing it a slice at a time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions