Skip to content

Nested spies cause memory leaks  #1532

@arturdryomov

Description

@arturdryomov

Seems like nested spies can cause memory leaks since such objects are kept in memory without purging. Not sure if it can be resolved at all. Should it be avoided? Is there a mention about this in docs? Anyway, the code speaks better and fortunately I’ve been able to create a self-contained sample.

BTW I can provide a .hprof file if you are interested.

Versions

org.mockito:mockito-core:2.22.0
org.mockito:mockito-inline:2.22.0
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

Gradle

Heap is set to 64 MB.

tasks.withType<Test> {
    maxHeapSize = "64m"
    jvmArgs("-XX:+HeapDumpOnOutOfMemoryError")

    failFast = true
}
$ ./gradlew :module:cleanTestDebugUnitTest :module:testDebugUnitTest --tests "com.github.sample.NestedSpiesMemoryLeakSpec"

Code

package com.github.sample

import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.functions.Consumer
import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.it
import org.junit.platform.runner.JUnitPlatform
import org.junit.runner.RunWith
import org.mockito.Mockito

@RunWith(JUnitPlatform::class)
class NestedSpiesMemoryLeakSpec : Spek({

    repeat(10_000) { iteration ->

        it("iteration [$iteration]") {
            Mockito.spy(Service())
        }

    }

}) {

    class Service {
        // Remove Mockito.spy and OOM disappears.
        val value = Mockito.spy(Consumer<Int> {
            // This closure keeps a reference to Service.
            streams.size
        })

        // See at as a mass to fill the RAM.
        val streams = (0..1_000).map { BehaviorRelay.create<Int>() }
    }

}
> Task :module:testDebugUnitTest
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to java_pid23350.hprof ...
Heap dump file created [99857779 bytes in 0.356 secs]

com.github.sample.NestedSpiesMemoryLeakSpec > it iteration [187] STANDARD_ERROR
    java.lang.OutOfMemoryError: GC overhead limit exceeded
    	at com.jakewharton.rxrelay2.BehaviorRelay.<init>(BehaviorRelay.java:99)
    	at com.jakewharton.rxrelay2.BehaviorRelay.create(BehaviorRelay.java:77)
    	at com.github.sample.NestedSpiesMemoryLeakSpec$Service.<init>(NestedSpiesMemoryLeakSpec.kt:32)
    	at com.github.sample.NestedSpiesMemoryLeakSpec$1$1$1.invoke(NestedSpiesMemoryLeakSpec.kt:17)
    	at com.github.sample.NestedSpiesMemoryLeakSpec$1$1$1.invoke(NestedSpiesMemoryLeakSpec.kt:12)
    	at org.jetbrains.spek.engine.Scope$Test.execute(Scope.kt:102)
    	at org.jetbrains.spek.engine.Scope$Test.execute(Scope.kt:80)
    	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:105)
    	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$82/547193480.execute(Unknown Source)
    	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
    	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)

com.github.sample.NestedSpiesMemoryLeakSpec > it iteration [187] FAILED
    java.lang.OutOfMemoryError

> Task :module:testDebugUnitTest FAILED

Eclipse Memory Analyzer

screen shot 2018-11-14 at 19 09 53

screen shot 2018-11-14 at 19 08 59

screen shot 2018-11-14 at 19 09 09


Seems like this happens:

  • Service is a spy.
  • Service contains a Consumer, it is a spy as well.
  • Consumer is a closure and has an implicit reference to Service.
  • Mockito keeps both spies and cannot remove them from memory since there is a cross-reference (I guess).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions