HHH-12467 ByteBuddy TypeCache stale entries should be cleared to avoid references to application classloader
This commit is contained in:
parent
85adc9ca27
commit
dba91ab8fe
|
@ -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 )
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" );
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue