diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java index 236d22c804..99516da477 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java @@ -19,7 +19,7 @@ import java.lang.invoke.VarHandle; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; - +import org.checkerframework.checker.nullness.qual.Nullable; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; @@ -82,7 +82,7 @@ public final class Specifications { public static final ParameterizedTypeName ASYNC_CACHE_LOADER = ParameterizedTypeName.get( ClassName.get(PACKAGE_NAME, "AsyncCacheLoader"), TypeVariableName.get("? super K"), vTypeVar); public static final ParameterSpec ASYNC_CACHE_LOADER_PARAM = - ParameterSpec.builder(ASYNC_CACHE_LOADER, "cacheLoader").build(); + ParameterSpec.builder(ASYNC_CACHE_LOADER, "cacheLoader").addAnnotation(Nullable.class).build(); public static final TypeName REMOVAL_LISTENER = ParameterizedTypeName.get( ClassName.get(PACKAGE_NAME, "RemovalListener"), kTypeVar, vTypeVar); diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddConstructor.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddConstructor.java index 5001ac4812..e0c0ed3a84 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddConstructor.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddConstructor.java @@ -16,8 +16,11 @@ package com.github.benmanes.caffeine.cache.local; import static com.github.benmanes.caffeine.cache.Specifications.ASYNC_CACHE_LOADER_PARAM; +import static com.github.benmanes.caffeine.cache.Specifications.LOCAL_CACHE_FACTORY; import static com.github.benmanes.caffeine.cache.Specifications.BOUNDED_LOCAL_CACHE; import static com.github.benmanes.caffeine.cache.Specifications.BUILDER_PARAM; +import javax.lang.model.element.Modifier; +import com.squareup.javapoet.FieldSpec; /** * Adds the constructor to the cache. @@ -44,5 +47,8 @@ protected void execute() { } else { context.constructor.addStatement("super(builder, cacheLoader, async)"); } + context.cache + .addField(FieldSpec.builder(LOCAL_CACHE_FACTORY, "FACTORY", Modifier.STATIC, Modifier.FINAL) + .initializer("$N::new", context.className).build()); } } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/LocalCacheContext.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/LocalCacheContext.java index 73a8c89504..29d7ee564a 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/LocalCacheContext.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/LocalCacheContext.java @@ -32,6 +32,7 @@ */ public final class LocalCacheContext { public final boolean isFinal; + public final String className; public final TypeName superClass; public final TypeSpec.Builder cache; public final MethodSpec.Builder constructor; @@ -42,6 +43,7 @@ public final class LocalCacheContext { public LocalCacheContext(TypeName superClass, String className, boolean isFinal, Set parentFeatures, Set generateFeatures) { this.isFinal = isFinal; + this.className = className; this.superClass = superClass; this.suppressedWarnings = new TreeSet<>(); this.cache = TypeSpec.classBuilder(className); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java index 00f87b8c4b..67408119ff 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java @@ -15,14 +15,15 @@ */ package com.github.benmanes.caffeine; +import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; - import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; /** * @author ben.manes@gmail.com (Ben Manes) @@ -34,23 +35,28 @@ public class FactoryBenchmark { private final MethodHandleFactory methodHandleFactory = new MethodHandleFactory(); @Benchmark - public Alpha direct() { - return new Alpha(); + public void direct(Blackhole blackhole) { + blackhole.consume(new Alpha()); } @Benchmark - public Alpha methodHandle_invoke() { - return methodHandleFactory.invoke(); + public void methodHandle_invoke(Blackhole blackhole) { + blackhole.consume(methodHandleFactory.invoke()); } @Benchmark - public Alpha methodHandle_invokeExact() { - return methodHandleFactory.invokeExact(); + public void methodHandle_invokeExact(Blackhole blackhole) { + blackhole.consume(methodHandleFactory.invokeExact()); } @Benchmark - public Alpha reflection() { - return reflectionFactory.newInstance(); + public void methodHandle_lambda(Blackhole blackhole) { + blackhole.consume(methodHandleFactory.lambda()); + } + + @Benchmark + public void reflection(Blackhole blackhole) { + blackhole.consume(reflectionFactory.newInstance()); } static final class MethodHandleFactory { @@ -58,11 +64,17 @@ static final class MethodHandleFactory { private static final MethodType METHOD_TYPE = MethodType.methodType(void.class); private final MethodHandle methodHandle; + private final AlphaConstructor lambda; MethodHandleFactory() { try { methodHandle = LOOKUP.findConstructor(Alpha.class, METHOD_TYPE); - } catch (NoSuchMethodException | IllegalAccessException e) { + lambda = + (AlphaConstructor) LambdaMetafactory + .metafactory(LOOKUP, "construct", MethodType.methodType(AlphaConstructor.class), + methodHandle.type(), methodHandle, methodHandle.type()) + .getTarget().invokeExact(); + } catch (Throwable e) { throw new RuntimeException(e); } } @@ -82,6 +94,10 @@ Alpha invokeExact() { throw new RuntimeException(e); } } + + Alpha lambda() { + return lambda.construct(); + } } static final class ReflectionFactory { @@ -107,4 +123,8 @@ Alpha newInstance() { static final class Alpha { public Alpha() {} } + + private interface AlphaConstructor { + Alpha construct(); + } } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java index 58b5fa5e02..b09d3fd065 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java @@ -15,10 +15,10 @@ */ package com.github.benmanes.caffeine.cache; -import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; - +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -26,12 +26,14 @@ * * @author ben.manes@gmail.com (Ben Manes) */ -final class LocalCacheFactory { - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - private static final MethodType FACTORY = MethodType.methodType( +interface LocalCacheFactory { + MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + MethodType FACTORY = MethodType.methodType( void.class, Caffeine.class, AsyncCacheLoader.class, boolean.class); + Map FACTORIES = new ConcurrentHashMap<>(); - private LocalCacheFactory() {} + BoundedLocalCache newInstance(Caffeine builder, + @Nullable AsyncCacheLoader cacheLoader, boolean async); /** Returns a cache optimized for this configuration. */ static BoundedLocalCache newBoundedLocalCache(Caffeine builder, @@ -41,7 +43,7 @@ static BoundedLocalCache newBoundedLocalCache(Caffeine builde } static String getClassName(Caffeine builder) { - var className = new StringBuilder(LocalCacheFactory.class.getPackageName()).append('.'); + var className = new StringBuilder(); if (builder.isStrongKeys()) { className.append('S'); } else { @@ -80,10 +82,38 @@ static String getClassName(Caffeine builder) { static BoundedLocalCache loadFactory(Caffeine builder, @Nullable AsyncCacheLoader cacheLoader, boolean async, String className) { + var factory = FACTORIES.get(className); + if (factory == null) { + factory = FACTORIES.computeIfAbsent(className, LocalCacheFactory::newFactory); + } + return factory.newInstance(builder, cacheLoader, async); + } + + static LocalCacheFactory newFactory(String className) { try { - Class clazz = Class.forName(className); - MethodHandle handle = LOOKUP.findConstructor(clazz, FACTORY); - return (BoundedLocalCache) handle.invoke(builder, cacheLoader, async); + var clazz = LOOKUP.findClass(LocalCacheFactory.class.getPackageName() + "." + className); + try { + // Fast path + return (LocalCacheFactory) LOOKUP + .findStaticVarHandle(clazz, "FACTORY", LocalCacheFactory.class).get(); + } catch (NoSuchFieldException e) { + // Slow path when native hints are missing the field, but may have the constructor. + var constructor = LOOKUP.findConstructor(clazz, FACTORY); + var methodHandle = + constructor.asType(constructor.type().changeReturnType(BoundedLocalCache.class)); + return new LocalCacheFactory() { + @Override + public BoundedLocalCache newInstance(Caffeine builder, + @Nullable AsyncCacheLoader cacheLoader, boolean async) { + try { + return (BoundedLocalCache) methodHandle.invokeExact(builder, cacheLoader, + async); + } catch (Throwable t) { + throw new IllegalStateException(className, t); + } + } + }; + } } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java index 162fc27911..fbdf89a3a3 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java @@ -15,10 +15,11 @@ */ package com.github.benmanes.caffeine.cache; -import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import com.github.benmanes.caffeine.cache.References.LookupKeyReference; import com.github.benmanes.caffeine.cache.References.WeakKeyReference; @@ -31,6 +32,7 @@ interface NodeFactory { MethodType FACTORY = MethodType.methodType(void.class); MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + Map> FACTORIES = new ConcurrentHashMap<>(); RetiredStrongKey RETIRED_STRONG_KEY = new RetiredStrongKey(); RetiredWeakKey RETIRED_WEAK_KEY = new RetiredWeakKey(); @@ -87,7 +89,7 @@ static NodeFactory newFactory(Caffeine builder, boolean isAsy } static String getClassName(Caffeine builder, boolean isAsync) { - var className = new StringBuilder(Node.class.getPackageName()).append('.'); + var className = new StringBuilder(); if (builder.isStrongKeys()) { className.append('P'); } else { @@ -131,11 +133,21 @@ static String getClassName(Caffeine builder, boolean isAsync) { return className.toString(); } + @SuppressWarnings("unchecked") static NodeFactory loadFactory(String className) { + var factory = FACTORIES.get(className); + if (factory == null) { + factory = FACTORIES.computeIfAbsent(className, NodeFactory::newFactory); + } + return (NodeFactory) factory; + } + + static NodeFactory newFactory(String className) { try { - Class clazz = Class.forName(className); - MethodHandle handle = LOOKUP.findConstructor(clazz, FACTORY); - return (NodeFactory) handle.invoke(); + var clazz = LOOKUP.findClass(Node.class.getPackageName() + "." + className); + var constructor = LOOKUP.findConstructor(clazz, FACTORY); + return (NodeFactory) constructor + .asType(constructor.type().changeReturnType(NodeFactory.class)).invokeExact(); } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java index 68475b5bc0..0142ed06e1 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java @@ -50,7 +50,6 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong;