Skip to content

Commit

Permalink
Escape mock during method dispatch on mock to avoid premature garbage…
Browse files Browse the repository at this point in the history
… collection.

Fixes #1802.
  • Loading branch information
raphw committed Sep 3, 2020
1 parent fcd788c commit 03afdae
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 8 deletions.
Expand Up @@ -36,6 +36,8 @@ public class MockMethodInterceptor implements Serializable {

private final ByteBuddyCrossClassLoaderSerializationSupport serializationSupport;

private final ThreadLocal<Object> weakReferenceHatch = new ThreadLocal<>();

public MockMethodInterceptor(MockHandler handler, MockCreationSettings mockCreationSettings) {
this.handler = handler;
this.mockCreationSettings = mockCreationSettings;
Expand All @@ -54,14 +56,29 @@ Object doIntercept(
RealMethod realMethod,
Location location)
throws Throwable {
return handler.handle(
createInvocation(
mock,
invokedMethod,
arguments,
realMethod,
mockCreationSettings,
location));
// If the currently dispatched method is used in a hot path, typically a tight loop and if
// the mock is not used after the currently dispatched method, the JVM might attempt a
// garbage collection of the mock instance even before the execution of the current
// method is completed. Since we only reference the mock weakly from hereon after to avoid
// leaking the instance, it might therefore be garbage collected before the handler.handle(...)
// method completes. Since the handler method expects the mock to be present while a method
// call onto the mock is dispatched, this can lead to the problem described in GitHub #1802.
//
// To avoid this problem, we distract the JVM JIT by escaping the mock instance to a thread local
// field for the duration of the handler's dispatch.
weakReferenceHatch.set(mock);
try {
return handler.handle(
createInvocation(
mock,
invokedMethod,
arguments,
realMethod,
mockCreationSettings,
location));
} finally {
weakReferenceHatch.remove();
}
}

public MockHandler getMockHandler() {
Expand Down
39 changes: 39 additions & 0 deletions src/test/java/org/mockito/PrematureGarbageCollectionTest.java
@@ -0,0 +1,39 @@
package org.mockito;

import org.junit.Test;

public class PrematureGarbageCollectionTest {

@Test
public void provoke_premature_garbage_collection() {
for (int i = 0; i < 500; i++) {
populateNodeList();
}
}

private static void populateNodeList() {
Node node = nodes();
while (node != null) {
Node next = node.next;
node.object.run();
node = next;
}
}

private static Node nodes() {
Node node = null;
for (int i = 0; i < 1_000; ++i) {
Node next = new Node();
next.next = node;
node = next;
}
return node;
}

private static class Node {

private Node next;

private final Runnable object = Mockito.mock(Runnable.class);
}
}

0 comments on commit 03afdae

Please sign in to comment.