HHH-12467 ByteBuddy TypeCache stale entries should be cleared to avoid references to application classloader

This commit is contained in:
Sanne Grinovero 2018-04-10 18:05:28 +01:00
parent 85adc9ca27
commit dba91ab8fe
7 changed files with 114 additions and 23 deletions

View File

@ -15,9 +15,7 @@ import org.hibernate.HibernateException;
import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.bytecode.spi.BasicProxyFactory;
import org.hibernate.proxy.ProxyConfiguration; import org.hibernate.proxy.ProxyConfiguration;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType; import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
@ -30,7 +28,7 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory {
private final Class proxyClass; private final Class proxyClass;
public BasicProxyFactoryImpl(Class superClass, Class[] interfaces) { public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState bytebuddy) {
if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) { if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) {
throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" ); throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" );
} }
@ -43,8 +41,7 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory {
key.addAll( Arrays.asList( interfaces ) ); key.addAll( Arrays.asList( interfaces ) );
} }
this.proxyClass = new ByteBuddy() this.proxyClass = bytebuddy.getCurrentyByteBuddy()
.with( TypeValidation.DISABLED )
.with( new AuxiliaryType.NamingStrategy.SuffixingRandom( "HibernateBasicProxy" ) ) .with( new AuxiliaryType.NamingStrategy.SuffixingRandom( "HibernateBasicProxy" ) )
.subclass( superClass == null ? Object.class : superClass ) .subclass( superClass == null ? Object.class : superClass )
.implement( interfaces == null ? NO_INTERFACES : interfaces ) .implement( interfaces == null ? NO_INTERFACES : interfaces )

View File

@ -0,0 +1,64 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.bytecode.internal.bytebuddy;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.TypeCache;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
/**
* An utility to hold all ByteBuddy related state, as in the current version of
* Hibernate the Bytecode Provider state is held in a static field, yet ByteBuddy
* is able to benefit from some caching and general state reuse.
*/
public final class ByteBuddyState {
private final ByteBuddy buddy = new ByteBuddy().with( TypeValidation.DISABLED );
/**
* This currently needs to be static: the BytecodeProvider is a static field of Environment and
* is being accessed from static methods.
* It will be easier to maintain the cache and its state when it will no longer be static
* in Hibernate ORM 6+.
* Opted for WEAK keys to avoid leaking the classloader in case the SessionFactory isn't closed.
* Avoiding Soft keys as they are prone to cause issues with unstable performance.
*/
private static final TypeCache<TypeCache.SimpleKey> CACHE = new TypeCache.WithInlineExpunction<TypeCache.SimpleKey>(
TypeCache.Sort.WEAK );
/**
* Access to ByteBuddy. It's almost equivalent to creating a new ByteBuddy instance,
* yet slightly preferrable so to be able to reuse the same instance.
* @return
*/
public ByteBuddy getCurrentyByteBuddy() {
return buddy;
}
/**
* @deprecated as we should not need static access to this state.
* This will be removed with no replacement.
* It's actually likely that this whole class becomes unnecessary in the near future.
*/
@Deprecated
public static TypeCache<TypeCache.SimpleKey> getCacheForProxies() {
return CACHE;
}
/**
* Wipes out all known caches used by ByteBuddy. This implies it might trigger the need
* to re-create some helpers if used at runtime, especially as this state is shared by
* multiple SessionFactory instances, but at least ensures we cleanup anything which is no
* longer needed after a SessionFactory close.
* The assumption is that closing SessionFactories is a rare event; in this perspective the cost
* of re-creating the small helpers should be negligible.
*/
void clearState() {
CACHE.clear();
}
}

View File

@ -19,11 +19,9 @@ import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.bytecode.spi.ProxyFactoryFactory; import org.hibernate.bytecode.spi.ProxyFactoryFactory;
import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.bytecode.spi.ReflectionOptimizer;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy; import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.MethodCall; import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender; import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
@ -34,15 +32,22 @@ import net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAware
import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type; import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
public class BytecodeProviderImpl implements BytecodeProvider { public class BytecodeProviderImpl implements BytecodeProvider {
private final ByteBuddy buddy = new ByteBuddy().with( TypeValidation.DISABLED ); private static final ByteBuddyState bytebuddy = new ByteBuddyState();
private static final NamingStrategy.SuffixingRandom instantiatorName = new NamingStrategy.SuffixingRandom( "HibernateInstantiator" );
private static final NamingStrategy.SuffixingRandom optimizerName = new NamingStrategy.SuffixingRandom( "HibernateAccessOptimizer" );
private static final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( "newInstance" );
private static final ElementMatcher.Junction getPropertyValuesMethodName = ElementMatchers.named( "getPropertyValues" );
private static final ElementMatcher.Junction setPropertyValuesMethodName = ElementMatchers.named( "setPropertyValues" );
private static final ElementMatcher.Junction getPropertyNamesMethodName = ElementMatchers.named( "getPropertyNames" );
@Override @Override
public ProxyFactoryFactory getProxyFactoryFactory() { public ProxyFactoryFactory getProxyFactoryFactory() {
return new ProxyFactoryFactoryImpl(); return new ProxyFactoryFactoryImpl( bytebuddy );
} }
@Override @Override
@ -56,23 +61,23 @@ public class BytecodeProviderImpl implements BytecodeProvider {
findAccessors( clazz, getterNames, setterNames, types, getters, setters ); findAccessors( clazz, getterNames, setterNames, types, getters, setters );
final Constructor<?> constructor = findConstructor( clazz ); final Constructor<?> constructor = findConstructor( clazz );
final Class fastClass = buddy final Class fastClass = bytebuddy.getCurrentyByteBuddy()
.with( new NamingStrategy.SuffixingRandom( "HibernateInstantiator" ) ) .with( instantiatorName )
.subclass( ReflectionOptimizer.InstantiationOptimizer.class ) .subclass( ReflectionOptimizer.InstantiationOptimizer.class )
.method( ElementMatchers.named( "newInstance" ) ) .method( newInstanceMethodName )
.intercept( MethodCall.construct( constructor ) ) .intercept( MethodCall.construct( constructor ) )
.make() .make()
.load( clazz.getClassLoader() ) .load( clazz.getClassLoader() )
.getLoaded(); .getLoaded();
final Class bulkAccessor = buddy final Class bulkAccessor = bytebuddy.getCurrentyByteBuddy()
.with( new NamingStrategy.SuffixingRandom( "HibernateAccessOptimizer" ) ) .with( optimizerName )
.subclass( ReflectionOptimizer.AccessOptimizer.class ) .subclass( ReflectionOptimizer.AccessOptimizer.class )
.method( ElementMatchers.named( "getPropertyValues" ) ) .method( getPropertyValuesMethodName )
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) ) .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) )
.method( ElementMatchers.named( "setPropertyValues" ) ) .method( setPropertyValuesMethodName )
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) ) .intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) )
.method( ElementMatchers.named( "getPropertyNames" ) ) .method( getPropertyNamesMethodName )
.intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) )
.make() .make()
.load( clazz.getClassLoader() ) .load( clazz.getClassLoader() )
@ -258,4 +263,9 @@ public class BytecodeProviderImpl implements BytecodeProvider {
public Enhancer getEnhancer(EnhancementContext enhancementContext) { public Enhancer getEnhancer(EnhancementContext enhancementContext) {
return new EnhancerImpl( enhancementContext ); return new EnhancerImpl( enhancementContext );
} }
public void resetCaches() {
bytebuddy.clearState();
}
} }

View File

@ -14,6 +14,12 @@ import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory;
public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory { public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory {
private final ByteBuddyState bytebuddy;
public ProxyFactoryFactoryImpl(ByteBuddyState bytebuddy) {
this.bytebuddy = bytebuddy;
}
@Override @Override
public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) { public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) {
return new ByteBuddyProxyFactory(); return new ByteBuddyProxyFactory();
@ -21,6 +27,6 @@ public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory {
@Override @Override
public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) { public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) {
return new BasicProxyFactoryImpl( superClass, interfaces ); return new BasicProxyFactoryImpl( superClass, interfaces, bytebuddy );
} }
} }

View File

@ -48,4 +48,16 @@ public interface BytecodeProvider {
* @return An enhancer to perform byte code manipulations. * @return An enhancer to perform byte code manipulations.
*/ */
Enhancer getEnhancer(EnhancementContext enhancementContext); Enhancer getEnhancer(EnhancementContext enhancementContext);
/**
* Some BytecodeProvider implementations will have classloader specific caching.
* These caches are useful at runtime but need to be reset at least on SessionFactory shutdown
* to prevent leaking the deployment classloader.
* Since the BytecodeProvider is static these caches are potentially shared across multiple
* deployments; in this case we'll clear all caches which might show as a small, temporary
* performance degradation on the SessionFactory instances which haven't been closed.
* This limitation will be removed in the future, when these providers will no longer be static.
*/
default void resetCaches() {}
} }

View File

@ -778,6 +778,8 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
* @throws HibernateException * @throws HibernateException
*/ */
public void close() throws HibernateException { public void close() throws HibernateException {
//This is an idempotent operation so we can do it even before the checks (it won't hurt):
Environment.getBytecodeProvider().resetCaches();
if ( isClosed ) { if ( isClosed ) {
if ( getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled() ) { if ( getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled() ) {
throw new IllegalStateException( "EntityManagerFactory is already closed" ); throw new IllegalStateException( "EntityManagerFactory is already closed" );

View File

@ -16,6 +16,7 @@ import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.ReflectHelper;
@ -43,9 +44,6 @@ import static net.bytebuddy.matcher.ElementMatchers.*;
public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { public class ByteBuddyProxyFactory implements ProxyFactory, Serializable {
private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class );
private static final TypeCache<TypeCache.SimpleKey> CACHE =
new TypeCache.WithInlineExpunction<TypeCache.SimpleKey>(TypeCache.Sort.SOFT);
private Class persistentClass; private Class persistentClass;
private String entityName; private String entityName;
private Class[] interfaces; private Class[] interfaces;
@ -92,7 +90,9 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable {
} }
key.addAll( Arrays.<Class<?>>asList( interfaces ) ); key.addAll( Arrays.<Class<?>>asList( interfaces ) );
return CACHE.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () -> final TypeCache<TypeCache.SimpleKey> cacheForProxies = ByteBuddyState.getCacheForProxies();
return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () ->
new ByteBuddy() new ByteBuddy()
.ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) )
.with(TypeValidation.DISABLED) .with(TypeValidation.DISABLED)
@ -108,7 +108,7 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable {
.intercept(FieldAccessor.ofField(ProxyConfiguration.INTERCEPTOR_FIELD_NAME).withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)) .intercept(FieldAccessor.ofField(ProxyConfiguration.INTERCEPTOR_FIELD_NAME).withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
.make() .make()
.load(persistentClass.getClassLoader()) .load(persistentClass.getClassLoader())
.getLoaded(), CACHE); .getLoaded(), cacheForProxies );
} }
@Override @Override