Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permgen memory leak due to ClassInfo caching in java.beans.Introspector on JDK 11/17 #27781

Closed
webbres opened this issue Dec 7, 2021 · 8 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Milestone

Comments

@webbres
Copy link

webbres commented Dec 7, 2021

I am currently trying to resolve an issue we're seeing with redeploying a spring boot application that looks to be a problem in the spring boot end not ours?

Links:

Config:
Java 11
OS: Windows & Centos
Spring boot: 2.5
Tomcat: 9.0.27


The problem/bug:

When deploying on tomcat 9 using java 11 reloading an application results in a PermGen memory leak with the classloader for the application not being GCd. After adding additional configuration only weak and soft references are found in the heap.

Summary of findings:

Reloading a spring boot application deployed as a war in tomcat 9 results in a PermGen memory leak (reported by the find leaks check and checked in VisualVM). In our actual application there is a strong reference to a log4j JMX bean which is removed when running tomcat with -Dlog4j2.disable.jmx=true though even then the org.apache.catalina.loader.ParallelWebappClassLoader for the application is marked as DESTROYED but is not garbage collected.

I attempted to recreate the issue on a simple spring boot starter (see repo linked). Here I found strong references to logging shutdown hooks which are removed when adding logging.register-shutdown-hook=false to the application.properties. Though after adding this property like above the classloader still isn't being garbage collected.

Cheers

Sam

@wilkinsona
Copy link
Member

As far as I can tell, this problem is specific to Java 11.

Running with Java 8, when an application is undeployed and GC is forced, all of its classes are unloaded and its ClassLoader is garbage collection. Running with Java 11, the ClassLoader is never garbage collected. In both cases, there are no strong references to the ClassLoader. In Java 8, classes loaded by the class loader are only referenced weakly. In Java 11, some of the classes loaded by the class loader are referenced softly. The presence of soft references is key to the difference in behaviour.

Generally speaking, weak references will be cleaned up whenever garbage collection is forced. By contrast, soft references will only be cleaned up when the JVM is under memory pressure. Examining in YourKit the heap of a basic Spring Boot web application running on Java 11 reveals soft references from com.sun.beans.introspect.ClassInfo to various Spring and application classes. These ClassInfo instances are being created by java.beans.Introspector via org.springframework.beans.CachedIntrospectionResults.

CachedIntrospectionResults used to call java.beans.Introspector.flushFromCaches(Class<?>) immediately after introspecting a class. This was removed as, due to changes in the JDK, it was no longer needed. It looks to me like it may be needed again following these changes in the JDK which introduced ClassInfo and a soft reference cache.

@snicoll snicoll transferred this issue from spring-projects/spring-boot Dec 7, 2021
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 7, 2021
@wilkinsona
Copy link
Member

wilkinsona commented Dec 7, 2021

Assuming that my analysis of the cause is correct, I am not sure that reinstating the call to flushFromCaches will be sufficient. As far as I can tell, it does not clear the cache used by the new com.sun.beans.introspect.ClassInfo. This was reported as a JDK-8207331 which was closed as a duplicate of JDK-8231454. JDK-8231454 was fixed in Java 16 by these changes.

@webbres
Copy link
Author

webbres commented Dec 8, 2021

Thank you for looking into it. It doesn't sound like there's much I can do my end in terms of support but do let me know if this isn't the case.

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jan 7, 2022

It sounds like this requires a fix in the JDK. @wilkinsona, I'm wondering if there anything to be done in the Spring Framework, i.e. should this remain open?

@rstoyanchev rstoyanchev added the in: core Issues in core modules (aop, beans, core, context, expression) label Jan 7, 2022
@wilkinsona
Copy link
Member

The fix has already been made in the JDK but only in Java 16 and later. For that fix to be effective Framework's call to flushFromCaches needs to be reinstated as far as I can tell. For Java 11, I'm not sure what can be done. At best, a change here would need to be a reflective hack as just calling flushFromCaches won't help. The ideal would be a backport of JDK-8231454 to Java 11.

@bclozel
Copy link
Member

bclozel commented Jul 17, 2023

This has been backported in Java 11.0.16 by JDK-8287343. The 6.0.x generation has moved to JDK17+ in the meantime.

I'm closing this issue as a result.

@bclozel bclozel closed this as not planned Won't fix, can't repro, duplicate, stale Jul 17, 2023
@bclozel bclozel added status: superseded An issue that has been superseded by another for: external-project Needs a fix in external project and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jul 17, 2023
@wilkinsona
Copy link
Member

@bclozel has the call to flushFromCaches been reinstated? If my analysis above is correct, it is required to take advantage of the JDK changes.

@bclozel bclozel reopened this Jul 17, 2023
@bclozel bclozel removed status: superseded An issue that has been superseded by another for: external-project Needs a fix in external project labels Jul 17, 2023
@bclozel bclozel added this to the General Backlog milestone Jul 17, 2023
@jhoeller jhoeller changed the title spring boot redeploy in tomcat causing a permgen memory leak Permgen memory leak due to ClassInfo caching in java.beans.Introspector on JDK 11/17 Aug 7, 2023
@jhoeller jhoeller self-assigned this Aug 7, 2023
@jhoeller jhoeller added the type: regression A bug that is also a regression label Aug 7, 2023
@jhoeller jhoeller modified the milestones: General Backlog, 6.0.12 Aug 7, 2023
@jhoeller
Copy link
Contributor

jhoeller commented Aug 7, 2023

As of 6.0, this only affects StandardBeanInfoFactory since our lean SimpleBeanInfoFactory (which does not call java.beans.Introspector) is used by default now. Nevertheless, the Introspector.flushFromCaches() is worth reinstating there as well in case someone explicitly configures StandardBeanInfoFactory for backwaeds compatibility.

More importantly, this is worth backporting to 5.3.x for avoiding the memory leak on JDK 11.0.16+.

@jhoeller jhoeller added the for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x label Aug 7, 2023
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x labels Aug 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

6 participants