Skip to content

Commit 6de6d36

Browse files
committed
HHH-14694 Use stable instantiator and access optimizer names to reduce generated classes
1 parent 73c490c commit 6de6d36

File tree

2 files changed

+106
-53
lines changed

2 files changed

+106
-53
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -169,18 +169,9 @@ Class<?> loadBasicProxy(Class<?> referenceClass, String proxyClassName,
169169
* @return The loaded generated class.
170170
*/
171171
public Class<?> load(Class<?> referenceClass, Function<ByteBuddy, DynamicType.Builder<?>> makeClassFunction) {
172-
Unloaded<?> result =
173-
make( makeClassFunction.apply( byteBuddy ) );
174-
if (DEBUG) {
175-
try {
176-
result.saveIn( new File( System.getProperty( "java.io.tmpdir" ) + "/bytebuddy/" ) );
177-
}
178-
catch (IOException e) {
179-
LOG.warn( "Unable to save generated class %1$s", result.getTypeDescription().getName(), e );
180-
}
181-
}
182-
return result.load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) )
183-
.getLoaded();
172+
return make( makeClassFunction.apply( byteBuddy ) )
173+
.load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) )
174+
.getLoaded();
184175
}
185176

186177
/**
@@ -228,15 +219,23 @@ void clearState() {
228219
basicProxyCache.clear();
229220
}
230221

231-
private Class<?> load(Class<?> referenceClass, String proxyClassName, BiFunction<ByteBuddy, NamingStrategy, DynamicType.Builder<?>> makeProxyFunction) {
222+
/**
223+
* Load a class generated by ByteBuddy.
224+
*
225+
* @param referenceClass The main class for which to create a class - might be an interface.
226+
* @param className The name under which the class shall be created.
227+
* @param makeClassFunction A function building the class.
228+
* @return The loaded generated class.
229+
*/
230+
public Class<?> load(Class<?> referenceClass, String className, BiFunction<ByteBuddy, NamingStrategy, DynamicType.Builder<?>> makeClassFunction) {
232231
try {
233-
return referenceClass.getClassLoader().loadClass( proxyClassName );
232+
return referenceClass.getClassLoader().loadClass( className );
234233
}
235234
catch (ClassNotFoundException e) {
236235
// Ignore
237236
}
238237
try {
239-
return make( makeProxyFunction.apply( byteBuddy, new FixedNamingStrategy( proxyClassName ) ) )
238+
return make( makeClassFunction.apply( byteBuddy, new FixedNamingStrategy( className ) ) )
240239
.load(
241240
referenceClass.getClassLoader(),
242241
resolveClassLoadingStrategy( referenceClass )
@@ -245,10 +244,10 @@ private Class<?> load(Class<?> referenceClass, String proxyClassName, BiFunction
245244
}
246245
catch (LinkageError e) {
247246
try {
248-
return referenceClass.getClassLoader().loadClass( proxyClassName );
247+
return referenceClass.getClassLoader().loadClass( className );
249248
}
250249
catch (ClassNotFoundException ex) {
251-
throw new RuntimeException( "Couldn't load or define class [" + proxyClassName + "]", e );
250+
throw new RuntimeException( "Couldn't load or define class [" + className + "]", e );
252251
}
253252
}
254253
}

hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java

Lines changed: 90 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.lang.reflect.Member;
1313
import java.lang.reflect.Method;
1414
import java.lang.reflect.Modifier;
15+
import java.nio.charset.StandardCharsets;
1516
import java.util.ArrayList;
1617
import java.util.Arrays;
1718
import java.util.Collections;
@@ -68,7 +69,6 @@
6869
import net.bytebuddy.jar.asm.Type;
6970
import net.bytebuddy.matcher.ElementMatcher;
7071
import net.bytebuddy.matcher.ElementMatchers;
71-
import net.bytebuddy.pool.TypePool;
7272
import org.checkerframework.checker.nullness.qual.Nullable;
7373

7474
public class BytecodeProviderImpl implements BytecodeProvider {
@@ -149,11 +149,9 @@ public ReflectionOptimizer getReflectionOptimizer(
149149
fastClass = null;
150150
}
151151
else {
152-
fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
153-
.with( new NamingStrategy.SuffixingRandom(
154-
INSTANTIATOR_PROXY_NAMING_SUFFIX,
155-
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() )
156-
) )
152+
final String className = clazz.getName() + "$" + INSTANTIATOR_PROXY_NAMING_SUFFIX;
153+
fastClass = byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy
154+
.with( namingStrategy )
157155
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
158156
.method( newInstanceMethodName )
159157
.intercept( MethodCall.construct( constructor ) )
@@ -213,11 +211,9 @@ public ReflectionOptimizer getReflectionOptimizer(
213211
fastClass = null;
214212
}
215213
else {
216-
fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
217-
.with( new NamingStrategy.SuffixingRandom(
218-
INSTANTIATOR_PROXY_NAMING_SUFFIX,
219-
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() )
220-
) )
214+
final String className = clazz.getName() + "$" + INSTANTIATOR_PROXY_NAMING_SUFFIX;
215+
fastClass = byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy
216+
.with( namingStrategy )
221217
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
222218
.method( newInstanceMethodName )
223219
.intercept( MethodCall.construct( constructor ) )
@@ -238,23 +234,41 @@ public ReflectionOptimizer getReflectionOptimizer(
238234
return null;
239235
}
240236

241-
Class<?> superClass = determineAccessOptimizerSuperClass( clazz, getters, setters );
242-
243237
final String[] propertyNames = propertyAccessMap.keySet().toArray( new String[0] );
244-
final Class<?> bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
245-
.with( new NamingStrategy.SuffixingRandom(
246-
OPTIMIZER_PROXY_NAMING_SUFFIX,
247-
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() )
248-
) )
249-
.subclass( superClass )
250-
.implement( ReflectionOptimizer.AccessOptimizer.class )
251-
.method( getPropertyValuesMethodName )
252-
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, propertyNames, getters ) ) )
253-
.method( setPropertyValuesMethodName )
254-
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters ) ) )
255-
.method( getPropertyNamesMethodName )
256-
.intercept( MethodCall.call( new CloningPropertyCall( propertyNames ) ) )
257-
);
238+
final Class<?> superClass = determineAccessOptimizerSuperClass( clazz, propertyNames, getters, setters );
239+
240+
final String className = clazz.getName() + "$" + OPTIMIZER_PROXY_NAMING_SUFFIX + encodeName( propertyNames, getters, setters );
241+
final Class<?> bulkAccessor;
242+
if ( className.getBytes( StandardCharsets.UTF_8 ).length >= 0x10000 ) {
243+
// The JVM has a 64K byte limit on class name length, so fallback to random name if encoding exceeds that
244+
bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
245+
.with( new NamingStrategy.SuffixingRandom(
246+
OPTIMIZER_PROXY_NAMING_SUFFIX,
247+
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() )
248+
) )
249+
.subclass( superClass )
250+
.implement( ReflectionOptimizer.AccessOptimizer.class )
251+
.method( getPropertyValuesMethodName )
252+
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, propertyNames, getters ) ) )
253+
.method( setPropertyValuesMethodName )
254+
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters ) ) )
255+
.method( getPropertyNamesMethodName )
256+
.intercept( MethodCall.call( new CloningPropertyCall( propertyNames ) ) )
257+
);
258+
}
259+
else {
260+
bulkAccessor = byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy
261+
.with( namingStrategy )
262+
.subclass( superClass )
263+
.implement( ReflectionOptimizer.AccessOptimizer.class )
264+
.method( getPropertyValuesMethodName )
265+
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, propertyNames, getters ) ) )
266+
.method( setPropertyValuesMethodName )
267+
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters ) ) )
268+
.method( getPropertyNamesMethodName )
269+
.intercept( MethodCall.call( new CloningPropertyCall( propertyNames ) ) )
270+
);
271+
}
258272

259273
try {
260274
return new ReflectionOptimizerImpl(
@@ -269,6 +283,7 @@ public ReflectionOptimizer getReflectionOptimizer(
269283

270284
private static class ForeignPackageClassInfo {
271285
final Class<?> clazz;
286+
final List<String> propertyNames = new ArrayList<>();
272287
final List<Member> getters = new ArrayList<>();
273288
final List<Member> setters = new ArrayList<>();
274289

@@ -277,7 +292,7 @@ public ForeignPackageClassInfo(Class<?> clazz) {
277292
}
278293
}
279294

280-
private Class<?> determineAccessOptimizerSuperClass(Class<?> clazz, Member[] getters, Member[] setters) {
295+
private Class<?> determineAccessOptimizerSuperClass(Class<?> clazz, String[] propertyNames, Member[] getters, Member[] setters) {
281296
if ( clazz.isInterface() ) {
282297
return Object.class;
283298
}
@@ -291,11 +306,17 @@ private Class<?> determineAccessOptimizerSuperClass(Class<?> clazz, Member[] get
291306
for ( int i = 0; i < getters.length; i++ ) {
292307
final Member getter = getters[i];
293308
final Member setter = setters[i];
309+
boolean found = false;
294310
if ( getter.getDeclaringClass() == foreignPackageClassInfo.clazz && !Modifier.isPublic( getter.getModifiers() ) ) {
295311
foreignPackageClassInfo.getters.add( getter );
312+
found = true;
296313
}
297314
if ( setter.getDeclaringClass() == foreignPackageClassInfo.clazz && !Modifier.isPublic( setter.getModifiers() ) ) {
298315
foreignPackageClassInfo.setters.add( setter );
316+
found = true;
317+
}
318+
if ( found ) {
319+
foreignPackageClassInfo.propertyNames.add( propertyNames[i] );
299320
}
300321
}
301322
if ( foreignPackageClassInfo.getters.isEmpty() && foreignPackageClassInfo.setters.isEmpty() ) {
@@ -307,16 +328,13 @@ private Class<?> determineAccessOptimizerSuperClass(Class<?> clazz, Member[] get
307328
for ( int i = foreignPackageClassInfos.size() - 1; i >= 0; i-- ) {
308329
final ForeignPackageClassInfo foreignPackageClassInfo = foreignPackageClassInfos.get( i );
309330
final Class<?> newSuperClass = superClass;
331+
332+
final String className = foreignPackageClassInfo.clazz.getName() + "$" + OPTIMIZER_PROXY_NAMING_SUFFIX + encodeName( foreignPackageClassInfo.propertyNames, foreignPackageClassInfo.getters, foreignPackageClassInfo.setters );
310333
superClass = byteBuddyState.load(
311334
foreignPackageClassInfo.clazz,
312-
byteBuddy -> {
313-
DynamicType.Builder<?> builder = byteBuddy.with(
314-
new NamingStrategy.SuffixingRandom(
315-
OPTIMIZER_PROXY_NAMING_SUFFIX,
316-
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue(
317-
foreignPackageClassInfo.clazz.getName() )
318-
)
319-
).subclass( newSuperClass );
335+
className,
336+
(byteBuddy, namingStrategy) -> {
337+
DynamicType.Builder<?> builder = byteBuddy.with( namingStrategy ).subclass( newSuperClass );
320338
for ( Member getter : foreignPackageClassInfo.getters ) {
321339
final Class<?> getterType;
322340
if ( getter instanceof Field ) {
@@ -386,6 +404,42 @@ private Class<?> determineAccessOptimizerSuperClass(Class<?> clazz, Member[] get
386404
return superClass;
387405
}
388406

407+
private static String encodeName(String[] propertyNames, Member[] getters, Member[] setters) {
408+
return encodeName( Arrays.asList( propertyNames ), Arrays.asList( getters ), Arrays.asList( setters ) );
409+
}
410+
411+
private static String encodeName(List<String> propertyNames, List<Member> getters, List<Member> setters) {
412+
final StringBuilder sb = new StringBuilder();
413+
for ( int i = 0; i < propertyNames.size(); i++ ) {
414+
final String propertyName = propertyNames.get( i );
415+
final Member getter = getters.get( i );
416+
final Member setter = setters.get( i );
417+
// Encode the two member types as 4 bit integer encoded as hex character
418+
sb.append( Integer.toHexString( getKind( getter ) << 2 | getKind( setter ) ) );
419+
sb.append( propertyName );
420+
}
421+
return sb.toString();
422+
}
423+
424+
private static int getKind(Member member) {
425+
// Encode the member type as 2 bit integer
426+
if ( member == EMBEDDED_MEMBER ) {
427+
return 0;
428+
}
429+
else if ( member instanceof Field ) {
430+
return 1;
431+
}
432+
else if ( member instanceof Method ) {
433+
return 2;
434+
}
435+
else if ( member instanceof ForeignPackageMember ) {
436+
return 3;
437+
}
438+
else {
439+
throw new IllegalArgumentException( "Unknown member type: " + member );
440+
}
441+
}
442+
389443
private static class ForeignPackageMember implements Member {
390444

391445
private final Class<?> foreignPackageAccessor;

0 commit comments

Comments
 (0)