Skip to content

Revise cache safety check to avoid performance regression in EAR packaged applications on WildFly [SPR-16714] #21255

Closed
@spring-projects-issues

Description

@spring-projects-issues

Diego Pettisani opened SPR-16714 and commented

We had a performance issue after upgraded from Spring 3.2.2 to 3.2.16.

Our application is packaged in an EAR file having several "skinny" WARs, so the content inside the EAR is:

.
|-- lib
|   -- spring-core-3.2.16.jar
|   -- spring-beans-3.2.16.jar
|   -- company-platform.jar
|   -- etc. etc.
|-- META-INF
|   -- application.xml
|-- app1.war
|   -- WEB-INF
|      -- web.xml
|      -- webapp-beans.xml
|-- app2.war
|   -- WEB-INF
|      -- web.xml
|      -- webapp-beans.xml
| -- more wars.....
.

In each web.xml file we have this configuration:

...
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>WEB-INF/webapp-beans.xml</param-value>
</context-param>
<context-param>
	<param-name>parentContextKey</param-name>
	<param-value>ear.context</param-value>
</context-param>
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
...

The parent context has several hundred of beans.

We developed an internal platform for our applications that often calls the following BeanFactory method:

org.springframework.beans.factory.BeanFactory.getBean(Class)

In turn, The above getBean(Class) method calls the following DefaultListableBeanFactory method:

org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(Class<?>, boolean, boolean)

After upgraded to Spring 3.2.16 the call to the above getBeanNamesForType() method was very slow and CPU consuming. The root cause is the following class loader check:

if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
	cache.put(type, resolvedBeanNames);
}

added for fixing a memory leak highlighted in the #16145 and released in Spring 3.2.9.

The check inside the if condition:

ClassUtils.isCacheSafe(type, getBeanClassLoader())

returns always false because:

  • type is an object of the EAR class loader
  • getBeanClassLoader() returns always the WAR class loader
  • the EAR class loader and the WAR class loader are different and they are sibling in the class loader hierarchy

So the following internal caches of the DefaultListableBeanFactory class:

/** Map of singleton and non-singleton bean names, keyed by dependency type */
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

/** Map of singleton-only bean names, keyed by dependency type */
private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

will always be empty and they will never be used.

Furthermore informations:

  • The application server used is Wildfly 10.0.0.Final
  • The JDK used is JDK8 update 74.
  • I supposed that upgrading our application to the Spring 4.x or 5.x version will not fix the performance regression because the class loader check is still there.

Affects: 3.2.18, 4.3.16, 5.0.5

Attachments:

Issue Links:

Referenced from: commits 46e3a91, 2989f01, 0efa7a0, 295929c

Backported to: 4.3.17

Metadata

Metadata

Assignees

Labels

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

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions