google / gwtmockito

Better GWT unit testing
https://google.github.io/gwtmockito
Apache License 2.0
157 stars 50 forks source link

Memory Leak in GwtMockitoTestRunner$GwtMockitoClassLoader #53

Open tcdavid opened 9 years ago

tcdavid commented 9 years ago

When running a large suite of JUnit test classes using the @GwtMockitoTestRunner, we consistently encounter a "java/lang/OutOfMemoryError".

We used the Eclipse Memory Analyzer to inspect the resulting heap dump file. The analysis identified the problem suspect as: 273 instances of "com.google.gwtmockito.GwtMockitoTestRunner$GwtMockitoClassLoader", loaded by "com.ibm.oti.vm.BootstrapClassLoader @ 0x8004b120" occupy 1,519,903,320 (94.34%) bytes.

We are running the following versions of the related libraries: JUnit - junit:junit:4.12 Mockito - org.mockito:mockito-all:1.10.19 GWTMockito - com.google.gwt.gwtmockito:gwtmockito:1.1.5

To run the tests, we are simply annotating our test classes with @RunWith(GwtMockitoTestRunner.class)

Is there something we need to do in a tearDown() method to make sure the GwtMockitoTestRunner releases the gwtMockitoClassLoader it created?

uplight-dev commented 9 years ago

Hi tcdavid,

Is there anything you or your team found during this time to fix this issue? Or +1 for an answer from the original developer of this library.

Thank you!

ekuefler commented 9 years ago

Hm this might be tricky to track down. Ideally GwtMockitoClassLoader shouldn't last longer than the test class, so if there're over 200 of them in your heap it's definitely either leaking somewhere or you're running a bunch of tests in parallel. Does the profiler you're using provide information on which classes are referencing GwtMockitoClassLoader? I think there are three possibilities:

If you can tell which classes are holding on to the reference and whether they're in your code vs. GwtMockito/JUnit's that would go a long way towards tracking down the problem.

schragmanuel commented 7 years ago

We have the same Problem in our project. Memory usage is 5GB with 3500 tests. Number of tests is going to grow, so we need to solve the problem somehow. I also see 266 instances of GwtMockitoTestRunner in the heap dump. This is exactly the number of tests I have with the @RunWith Annotation. Would it help if I provide you the dump @ekuefler ? Because I don't see how to get closer to the source of the problem.

Thank you!

Numerinico commented 6 years ago

Hi there,

Same problem here, huge memory consumption, any workaround ?

Cheers,

ekuefler commented 6 years ago

If anyone has collected a heap dump that would definitely be useful for tracking down the problem.

schragmanuel commented 6 years ago

I collected two heap dumps with visual vm. Files are available on wetransfer The files are getting deleted within 7 days, so please download them as soon as possible.

2018-06-22 16_27_37-visualvm 1 3 8

ekuefler commented 6 years ago

Sweet, thanks! I downloaded the dumps and will be able to analyze them closer before too long.

Numerinico commented 5 years ago

Hi there, any news ? :)

cinlloc commented 5 years ago

@ekuefler it seems it's a bit of both your first and third point:

  • It's being referenced from GwtMockitoTestRunner
  • It's being referenced by some class loaded by the classloader

It seems the first problem is really a JUnit4 one, because I can reproduce it with BlockJUnit4Runner (without memory leak because no additional reference to a classloader in the object). As you said, workaround is possible (gwtMockitoClassLoader = null in finally clause for example). The big matter is the second one. There is a bunch of objects that references the class loader, without saving any object like you said, so it should definitely be a leak of some kind.

Instances that references class loader

cinlloc commented 5 years ago

For those interested I have a workaround, if you use Maven: use the maven surefire plugin, that allows to execute every test class in a different Java process. Have to configure the plugin with these settings in your pom.xml:


<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <configuration>
        <forkCount>1</forkCount>
        <reuseForks>false</reuseForks>
      </configuration>
    </plugin>
  </plugins>
</build>
LudoPL commented 5 years ago

Hi, The issue is linked to the use of "ThreadLocal" by Mockito (mentioned here and here). When Mockito is used inside a unit test executed by the GwtMockitoTestRunner, instances of DefaultMockitoConfiguration and MockingProgressImpl objects are stored (as value) inside the ThreadLocalMap of the Thread executing the test. These objects are instantiate through the GwtMockitoClassLoader created for the test, so they prevent the garbage collection of this GwtMockitoClassLoader. The ThreadLocal instances used as keys in the ThreadLocalMap are referenced as static attributes of a class loaded by the GwtMockitoClassLoader, so even if WeakReference are used in the ThreadLocalMap, they won't be garbage collected too (before the GwtMockitoClassLoader is). As a result, for each test the created GwtMockitoClassLoader stay in memory (which also prevent the garbage collection of the GwtMockitoTestRunner as the GwtMockitoClassLoader is an internal class of it). I will try to propose a fix very soon, not sure it will be the proper way to do it but I have already done a POC that is working.

ekuefler commented 4 years ago

I've pushed a new snapshot version that contains this change: https://oss.sonatype.org/content/repositories/snapshots/com/google/gwt/gwtmockito/gwtmockito/1.1.9-SNAPSHOT/

Has anyone had a chance to try the new annotation yet and verify whether it's working as expected?