While load-testing our webapp, I found that threads servicing requests would block on the mergedBeanDefinitions map in getMergedBeanDefinitions, line 990 in the following:
http://springframework.cvs.sourceforge.net/springframework/spring/src/org/springframework/beans/factory/support/AbstractBeanFactory.java?revision=1.199&view=markup
What was happening was none of my Spring beans were being cached in mergedBeanDefinitions, so each time getMergedBeanDefinition(String) would call getMergedLocalBeanDefinition(String) it would need to reach back down into getMergedBeanDefinition(String, BeanDefinition, BeanDefinition) and grab the mergedBeanDefinitions lock.
Upon further inspection, on lines 1041-1043, I see this:
// Only cache the merged bean definition if we're already about to create an
// instance of the bean, or at least have already created an instance before.
if (containingBd == null && isCacheBeanMetadata() && this.alreadyCreated.contains(beanName)) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
Note how the logic in the predicate belies what the comment is saying. If I read the comment correctly, it's really saying this:
if (isCacheBeanMetadata() && (containingBd == null || this.alreadyCreated.contains(beanName))) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
When I change the code to do this, I no longer experience lock contention because my beans are now cached in mergedBeanDefinitions, and my pages show up quite quickly (from 10 seconds to ~1-2).
I'm not terribly familiar with the internals of Spring so I'm not sure if this is correct. Plus I'm using @SpringBean in Wicket which may be part of the problem, but even so this seems like a serious issue with the code path here, particularly with the predicate logic above and the level of lock contention I was observing – with 40 threads issuing requests against my webapp I was seeing latencies of up to 500 ms on this single lock.