Closed
Description
Affects: 5.2.20
Our production application shows 2.8% of spent cpu coming from the following stack trace:
java.lang.Object.hashCode
org.springframework.util.ObjectUtils.nullSafeHashCode
org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource.hashCode
org.springframework.aop.framework.CglibAopProxy$HashCodeInterceptor.intercept
<proprietary>$SpringCGLIB-Proxy.hashCode
<proprietary>.hashCode
Reviewing the code for AbstractbeanFactoryBasedTargetSource.hashCode()
, we see:
@Override
public int hashCode() {
int hashCode = getClass().hashCode();
hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.beanFactory);
hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.targetBeanName);
return hashCode;
}
this.targetBeanName
is a String
, and therefore never routes toObject.hashCode()
. That leaves this.beanFactory
as the only possible code path, and indeed there is no hashCode()
defined on the interface or any bundled implementation.
Modifying our custom bean factory (extends DefaultListableBeanFactoryWithPublicMutex
) with the following methods solves the problem:
/**
* This should be a singleton, hashcode doesn't matter unless somebody else is
* silly enough to use it. See AbstractBeanFactoryBasedTargetSource.hashCode
* (which uses it).
*
* This implementation is effectively free, and certainly much faster than
* Object.hashCode().
*/
@Override
public int hashCode() {
return 0;
}
/**
* This should be a singleton, use identity equals.
*/
@Override
public boolean equals(Object o) {
return (this == o);
}
I have two suggestions for how to solve this within the framework:
- Modify
AbstractBeanFactoryBasedTargetSource.hashCode()
to not consider the bean factory. This is okay because 1) bean factory cardinality is extremely low and most beans have the same factory, and 2) the identity check in equals is nearly-free and, frankly, faster than calculating the hashcode. - Accelerate the
hashCode()
method by implementing the method on the most common superclasses. One suggestion is to calculate a random number on object instantiation and keep that around as the hashcode. This serves the purpose of almost-uniquely identifying theBeanFactory
instance, and the memory penalty of anint
is small compared to the overall usage of mostBeanFactory
implementations.