Skip to content

Method injection causes memory leak [SPR-10785] #15411

Closed
@spring-projects-issues

Description

@spring-projects-issues
Collaborator

Christian Schreder opened SPR-10785 and commented

Overview

Recently I recognized strange behavior in a project with around 700 tests running via Maven in one JVM. Some of the tests have different application contexts defined. As I run the tests I get an OutOfMemoryException. I managed to break it down to one place where a bean with method injection is defined. If I comment it out I have constant memory usage. With it, memory consumption is increasing. Reproduced on Windows 7 using JRockit 1.6.

For further investigation I wrote a reproducer. It is a simple maven project defining two beans where one bean has a method injection. I have 4 test classes with 1000 test methods per class and after each method the application context is rebuilt (via the @DirtiesContext annotation). Also I included a log file with activated gc-logging. I was also able to reproduce this behavior on Mac OS 10.6.8 with Sun's JDK 1.6.

Assumptions

  • ApplicationContext with method injection.
  • Several tests with different application contexts or tests which dirty the application context => what matters is that application contexts get built often.

Steps to Reproduce

Run all tests in one JVM (e.g. with maven).

Result

Memory consumption constantly increases until an OutOfMemoryException is thrown.


Analysis

The following table displays execution time, the number of classes loaded, and memory consumption when the test suite in the attached ZIP file are executed against various Java and Spring versions.

Spring 3.2.x

|| Spring Version || Java Version || Time || Classes || Memory |
| 3.2.7.RELEASE | JDK 1.6 | 1:54 | 6700 | 54 / 86 / 86 |
| 3.2.7.RELEASE | JDK 1.7 | 1:51 | 6629 | 45 / 82 / 86 |
| 3.2.7.RELEASE | JDK 1.8 | 1:44 | 6670 | 43 / 44 / 1082 |
| 3.2.8.BUILD-SNAPSHOT | JDK 1.6 | 1:42 | 2716 | 21 / 22 / 86 |
| 3.2.8.BUILD-SNAPSHOT | JDK 1.7 | 1:31 | 2629 | 17 / 22 / 86 |
| 3.2.8.BUILD-SNAPSHOT | JDK 1.8 | 1:24 | 2683 | 17 / 18 / 1082 |

Spring 4.0.x

|| Spring Version || Java Version || Time || Classes || Memory |
| 4.0.1.RELEASE | JDK 1.6 | 2:27 | 6761 | 54 / 83 / 86 |
| 4.0.1.RELEASE | JDK 1.7 | 2:22 | 6681 | 46 / 78 / 86 |
| 4.0.1.RELEASE | JDK 1.8 | 2:09 | 6739 | 43 / 44 / 1082 |
| 4.0.2.BUILD-SNAPSHOT | JDK 1.6 | 1:38 | 2758 | 22 / 22 / 86 |
| 4.0.2.BUILD-SNAPSHOT | JDK 1.7 | 1:56 | 2675 | 17 / 22 / 86 |
| 4.0.2.BUILD-SNAPSHOT | JDK 1.8 | 1:39 | 2741 | 18 / 19 / 1082 |

Key
  • OS: Mac OS X 10.9.1
  • Java Versions:
    JDK Version
    1.6 1.6.0_65-b14-462-11M4609
    1.7 1.7.0_51-b13
    1.8 1.8.0-b129
  • Time: in minutes
  • Classes: represent the number of classes loaded.
  • Memory: values are rounded and represent Used/Size/Max memory * 1,000,000 Bytes. For JDK 1.6 and 1.7, memory represents PermGen usage. For JDK 1.8, memory represents Metaspace usage.

Conclusion

As can be seen in the Analysis section, the fixes in Spring Framework 3.2.8.BUILD-SNAPSHOT and 4.0.2.BUILD-SNAPSHOT result in constant memory consumption and zero unnecessary CGLIB class generation (i.e., no longer one CGLIB class per method injected bean instance).

The following screenshots provide a visual comparison for Java 6.

Before Fix (Spring 3.2.7)

!memory usage - JDK 1.6 - Spring 3.2.7.png|thumbnail!

After Fix (Spring 4.0.2)

!memory usage - JDK 1.6 - Spring 4.0.2.png|thumbnail!


Affects: 3.0 GA

Attachments:

Issue Links:

Referenced from: commits 8028eae, 1ae3eba, f2a4537, bc87910

Backported to: 3.2.8

Activity

spring-projects-issues

spring-projects-issues commented on Feb 8, 2014

@spring-projects-issues
CollaboratorAuthor

Sam Brannen commented

Hi Christian,

Thanks for putting together the project for reproducing this behavior.

On a Mac, I was not able to reproduce the problem with JDK 7 or JDK 8 running against Spring Framework 4.0.2 (snapshot); however, I was able to reproduce it when running against JDK 6 on the same machine.

Thus it appears to chiefly be an issue with garbage collection in the JVM for Java 6.

However, the core of the issue likely lies in the fact that CGLIB is used to dynamically create subclasses of the bean class for which the method is injected. Specifically, the two variants of instantiateWithMethodInjection() in CglibSubclassingInstantiationStrategy do not cache the CGLIB Enhancer. As a consequence, a new dynamic subclass is created for every creation of the bean in question (i.e., every time the ApplicationContext is started), and these generated subclasses remain in the class loader and do not get garbage collected. This might not be avoidable since the underlying BeanFactory is in fact a different instance each time, but there still might be some room for improvement here.

We will continue to investigate the issue.

Regards,

Sam

spring-projects-issues

spring-projects-issues commented on Feb 8, 2014

@spring-projects-issues
CollaboratorAuthor

Sam Brannen commented

This potentially relates to #15899 in terms of recreation of identical CGLIB proxy classes.

spring-projects-issues

spring-projects-issues commented on Feb 11, 2014

@spring-projects-issues
CollaboratorAuthor

Sam Brannen commented

In case you're interested, I have pushed some work I've been doing on this issue to the following branch:

https://github.com/sbrannen/spring-framework/commits/SPR-10785

There are a few things to fine tune, but in general this approach seems to take care of the memory leaks associated with method injection.

spring-projects-issues

spring-projects-issues commented on Feb 12, 2014

@spring-projects-issues
CollaboratorAuthor

Sam Brannen commented

Resolving this issue as Fixed.

The fix was actually performed as a positive side-effect of #16047; however, I am leaving this issue assigned to both 3.2.8 and 4.0.2 since the issue summary is more explanatory than that for #16047.

spring-projects-issues

spring-projects-issues commented on Feb 13, 2014

@spring-projects-issues
CollaboratorAuthor

Sam Brannen commented

Although this issue has been fixed (see the Analysis and Conclusion sections above), we would like to point out that the Spring Team no longer recommends heavy usage of method injection.

As a modern alternative to method injection, please consider using the javax.inject.Provider<T> API instead. With Spring you can inject a Provider into a component and then call provider.get() to get an instance of the type T of object provided by that provider.

spring-projects-issues

spring-projects-issues commented on Oct 16, 2015

@spring-projects-issues
CollaboratorAuthor

Piotr Findeisen commented

Hi,
I'm migrating the application from Spring 3 to 4. It appears, changes here are not backwards compatible.
The difference manifest when bean has lookup-method and calls it in its constructor. Previously this worked, as CglibSubclassCreator set callbacks before instantiating. Now, during instantiation the stack looks like (oldest frame on top):

  • cglib-generated subclass ctor
  • user class ctor
  • cglib-generated override of the abstract method -- but the interceptor is not (yet) registered, so it forwards the call to super
  • user class's abstract method call cases AbstractMethodError

Of course, for every found case of this problem, the fix is as simple as moving the ctor code to @PostConstruct - unless this is up in the inheritance chain and subclasses' ctors rely on parent ctor having init'ed the data. Nonetheless, this is backwards incompatible change, and perhaps should be mentioned on the migration documentation page (I don't think it was mentioned there)

spring-projects-issues

spring-projects-issues commented on Oct 16, 2015

@spring-projects-issues
CollaboratorAuthor

Sam Brannen commented

Piotr Findeisen,

Since this is a closed issue, would you please create a new JIRA issue describing your findings?

Please also mention this issue in the new issue's description, and feel free to assign the issue to me.

Thanks!

Sam

spring-projects-issues

spring-projects-issues commented on Oct 17, 2015

@spring-projects-issues
CollaboratorAuthor

Piotr Findeisen commented

Sam Brannen,
I guess this change of behavior is simply the cost of fixing the mem leak (#15411). I'm not very inclined to report bug that cannot be fixed. Not sure it's worth it.

spring-projects-issues

spring-projects-issues commented on Oct 17, 2015

@spring-projects-issues
CollaboratorAuthor

Sam Brannen commented

Makes sense. Thanks for the feedback!

added
status: backportedAn issue that has been backported to maintenance branches
in: coreIssues in core modules (aop, beans, core, context, expression)
on Jan 11, 2019
added this to the 4.0.2 milestone on Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: backportedAn issue that has been backported to maintenance branchestype: bugA general bug

Type

No type

Projects

No projects

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @sbrannen@spring-projects-issues

      Issue actions

        Method injection causes memory leak [SPR-10785] · Issue #15411 · spring-projects/spring-framework