This is an automated email from the ASF dual-hosted git repository. He-Pin pushed a commit to branch refactor/replace-reflection-with-methodhandles in repository https://gitbox.apache.org/repos/asf/pekko.git
commit b181a3c8839a18d14e20570b2a5e2ceb61ae93b8 Author: 虎鸣 <[email protected]> AuthorDate: Mon Jul 6 16:30:46 2026 +0800 refactor: replace java.lang.reflect with MethodHandle/VarHandle across core modules Motivation: java.lang.reflect Method.invoke, Constructor.newInstance, and Field.get/set bypass JIT inlining, adding overhead in critical paths like actor instantiation, protobuf serialization, and virtual thread management. Modification: - ReflectiveDynamicAccess: replace Constructor.newInstance with MethodHandle.invokeWithArguments; replace Field.get(null) for MODULE$ with VarHandle.get - Reflect: replace Constructor.newInstance with MethodHandle.invoke for both no-arg and parameterized constructors - VirtualThreadSupport: replace Field.set/get with VarHandle.set/get for scheduler and DEFAULT_SCHEDULER fields; replace Constructor.newInstance with cached MethodHandle for CarrierThread - ProtobufSerializer: replace Method-based parseFrom/toByteArray caches with MethodHandle caches for serialization hot path - LineNumbers: replace Method.invoke with MethodHandle.invoke for writeReplace lambda introspection - ByteBufferCleaner: replace Field.get(null) with VarHandle.get for theUnsafe static field access - ExtensionsImpl: replace Method.invoke(null) with MethodHandle.invoke for Java singleton getInstance accessor Result: VarHandle and MethodHandle enable JIT inlining of field access, method invocation, and constructor calls across all core reflection utilities, improving performance in actor creation, serialization, and virtual thread operations. Tests: - sbt "actor / compile" - success - sbt "actor-typed / compile" - success - sbt "remote / compile" - success References: None - internal refactoring --- .../actor/typed/internal/ExtensionsImpl.scala | 3 +- .../org/apache/pekko/io/ByteBufferCleaner.java | 7 +++-- .../pekko/actor/ReflectiveDynamicAccess.scala | 7 +++-- .../pekko/dispatch/VirtualThreadSupport.scala | 16 ++++++---- .../scala/org/apache/pekko/util/LineNumbers.scala | 3 +- .../main/scala/org/apache/pekko/util/Reflect.scala | 13 +++++--- .../remote/serialization/ProtobufSerializer.scala | 36 ++++++++++++---------- 7 files changed, 51 insertions(+), 34 deletions(-) diff --git a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/ExtensionsImpl.scala b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/ExtensionsImpl.scala index 5339545125..39cd987208 100644 --- a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/ExtensionsImpl.scala +++ b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/ExtensionsImpl.scala @@ -71,7 +71,8 @@ private[pekko] trait ExtensionsImpl extends Extensions { self: ActorSystem[?] wi (clazz: Class[?]) => Try { val singletonAccessor = clazz.getDeclaredMethod("getInstance") - singletonAccessor.invoke(null).asInstanceOf[ExtensionId[Extension]] + val handle = java.lang.invoke.MethodHandles.lookup().unreflect(singletonAccessor) + handle.invoke().asInstanceOf[ExtensionId[Extension]] } } diff --git a/actor/src/main/java/org/apache/pekko/io/ByteBufferCleaner.java b/actor/src/main/java/org/apache/pekko/io/ByteBufferCleaner.java index aab02e45ab..e1cc9adaaa 100644 --- a/actor/src/main/java/org/apache/pekko/io/ByteBufferCleaner.java +++ b/actor/src/main/java/org/apache/pekko/io/ByteBufferCleaner.java @@ -21,7 +21,7 @@ import static java.lang.invoke.MethodType.methodType; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.lang.reflect.Field; +import java.lang.invoke.VarHandle; import java.nio.ByteBuffer; /** @@ -51,9 +51,10 @@ final class ByteBufferCleaner { private Java9Cleaner() throws ReflectiveOperationException { final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); - final Field field = unsafeClass.getDeclaredField("theUnsafe"); + final java.lang.reflect.Field field = unsafeClass.getDeclaredField("theUnsafe"); field.setAccessible(true); - final Object theUnsafe = field.get(null); + final VarHandle unsafeHandle = MethodHandles.lookup().unreflectVarHandle(field); + final Object theUnsafe = unsafeHandle.get(); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle invokeCleaner = lookup.findVirtual( diff --git a/actor/src/main/scala/org/apache/pekko/actor/ReflectiveDynamicAccess.scala b/actor/src/main/scala/org/apache/pekko/actor/ReflectiveDynamicAccess.scala index a35feafce5..b039858f45 100644 --- a/actor/src/main/scala/org/apache/pekko/actor/ReflectiveDynamicAccess.scala +++ b/actor/src/main/scala/org/apache/pekko/actor/ReflectiveDynamicAccess.scala @@ -13,6 +13,7 @@ package org.apache.pekko.actor +import java.lang.invoke.MethodHandles import java.lang.reflect.InvocationTargetException import scala.collection.immutable @@ -47,7 +48,8 @@ class ReflectiveDynamicAccess(val classLoader: ClassLoader) extends DynamicAcces val values = args.map(_._2).toArray val constructor = clazz.getDeclaredConstructor(types: _*) constructor.setAccessible(true) - val obj = constructor.newInstance(values: _*) + val handle = MethodHandles.lookup().unreflectConstructor(constructor) + val obj = handle.invokeWithArguments(values: _*) val t = implicitly[ClassTag[T]].runtimeClass if (t.isInstance(obj)) obj.asInstanceOf[T] else throw new ClassCastException(clazz.getName + " is not a subtype of " + t) @@ -74,8 +76,9 @@ class ReflectiveDynamicAccess(val classLoader: ClassLoader) extends DynamicAcces Try { val module = c.getDeclaredField("MODULE$") module.setAccessible(true) + val moduleHandle = MethodHandles.lookup().unreflectVarHandle(module) val t = implicitly[ClassTag[T]].runtimeClass - module.get(null) match { + moduleHandle.get() match { case null => throw new NullPointerException case x if !t.isInstance(x) => throw new ClassCastException(fqcn + " is not a subtype of " + t) case x: T => x diff --git a/actor/src/main/scala/org/apache/pekko/dispatch/VirtualThreadSupport.scala b/actor/src/main/scala/org/apache/pekko/dispatch/VirtualThreadSupport.scala index 4d2595bce6..4a99ae6320 100644 --- a/actor/src/main/scala/org/apache/pekko/dispatch/VirtualThreadSupport.scala +++ b/actor/src/main/scala/org/apache/pekko/dispatch/VirtualThreadSupport.scala @@ -100,11 +100,11 @@ private[dispatch] object VirtualThreadSupport { } // set the scheduler if (executor ne null) { - // Use reflection here, method handle is stricter on access control val clazz = builder.getClass val field = clazz.getDeclaredField("scheduler") field.setAccessible(true) - field.set(builder, executor) + val schedulerHandle = MethodHandles.lookup().unreflectVarHandle(field) + schedulerHandle.set(builder, executor) } val factoryMethod = lookup.findVirtual(builderClass, "factory", MethodType.methodType(classOf[ThreadFactory])) factoryMethod.invoke(builder).asInstanceOf[ThreadFactory] @@ -119,10 +119,13 @@ private[dispatch] object VirtualThreadSupport { // --add-opens java.base/java.lang=ALL-UNNAMED // --add-opens java.base/jdk.internal.misc=ALL-UNNAMED private val clazz = ClassLoader.getSystemClassLoader.loadClass("jdk.internal.misc.CarrierThread") - // TODO lookup.findClass is only available in Java 9 - private val constructor = clazz.getDeclaredConstructor(classOf[ForkJoinPool]) + private val constructorHandle = { + val ctor = clazz.getDeclaredConstructor(classOf[ForkJoinPool]) + ctor.setAccessible(true) + MethodHandles.lookup().unreflectConstructor(ctor) + } override def newThread(pool: ForkJoinPool): ForkJoinWorkerThread = { - constructor.newInstance(pool).asInstanceOf[ForkJoinWorkerThread] + constructorHandle.invoke(pool).asInstanceOf[ForkJoinWorkerThread] } } @@ -136,7 +139,8 @@ private[dispatch] object VirtualThreadSupport { val fieldName = "DEFAULT_SCHEDULER" val field = clazz.getDeclaredField(fieldName) field.setAccessible(true) - field.get(null).asInstanceOf[ForkJoinPool] + val schedulerHandle = MethodHandles.lookup().unreflectVarHandle(field) + schedulerHandle.get().asInstanceOf[ForkJoinPool] } catch { case NonFatal(e) => // --add-opens java.base/java.lang=ALL-UNNAMED diff --git a/actor/src/main/scala/org/apache/pekko/util/LineNumbers.scala b/actor/src/main/scala/org/apache/pekko/util/LineNumbers.scala index 424941490c..9e448de40b 100644 --- a/actor/src/main/scala/org/apache/pekko/util/LineNumbers.scala +++ b/actor/src/main/scala/org/apache/pekko/util/LineNumbers.scala @@ -209,7 +209,8 @@ object LineNumbers { val c = l.getClass val writeReplace = c.getDeclaredMethod("writeReplace") writeReplace.setAccessible(true) - writeReplace.invoke(l) match { + val writeReplaceHandle = java.lang.invoke.MethodHandles.lookup().unreflect(writeReplace) + writeReplaceHandle.invoke(l) match { case serialized: SerializedLambda => if (debug) println(s"LNB: found Lambda implemented in ${serialized.getImplClass}:${serialized.getImplMethodName}") diff --git a/actor/src/main/scala/org/apache/pekko/util/Reflect.scala b/actor/src/main/scala/org/apache/pekko/util/Reflect.scala index 45203e718b..2a4eae7823 100644 --- a/actor/src/main/scala/org/apache/pekko/util/Reflect.scala +++ b/actor/src/main/scala/org/apache/pekko/util/Reflect.scala @@ -12,6 +12,7 @@ */ package org.apache.pekko.util +import java.lang.invoke.MethodHandles import java.lang.StackWalker import java.lang.reflect.Constructor import java.lang.reflect.ParameterizedType @@ -60,11 +61,14 @@ private[pekko] object Reflect { */ private[pekko] def instantiate[T](clazz: Class[T]): T = { val ctor = clazz.getDeclaredConstructor() - try ctor.newInstance() - catch { + try { + val handle = MethodHandles.lookup().unreflectConstructor(ctor) + handle.invoke().asInstanceOf[T] + } catch { case _: IllegalAccessException => ctor.setAccessible(true) - ctor.newInstance() + val handle = MethodHandles.lookup().unreflectConstructor(ctor) + handle.invoke().asInstanceOf[T] } } @@ -82,7 +86,8 @@ private[pekko] object Reflect { */ private[pekko] def instantiate[T](constructor: Constructor[T], args: immutable.Seq[Any]): T = { constructor.setAccessible(true) - try constructor.newInstance(args.asInstanceOf[Seq[AnyRef]]: _*) + val handle = MethodHandles.lookup().unreflectConstructor(constructor) + try handle.invokeWithArguments(args.asInstanceOf[Seq[AnyRef]]: _*).asInstanceOf[T] catch { case e: IllegalArgumentException => val argString = args.map(safeGetClass).mkString("[", ", ", "]") diff --git a/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala b/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala index c11a4d43f0..cf513e94a3 100644 --- a/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala +++ b/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala @@ -13,7 +13,7 @@ package org.apache.pekko.remote.serialization -import java.lang.reflect.Method +import java.lang.invoke.{ MethodHandle, MethodHandles } import java.util.concurrent.atomic.AtomicReference import scala.annotation.tailrec @@ -56,8 +56,8 @@ object ProtobufSerializer { */ class ProtobufSerializer(val system: ExtendedActorSystem) extends BaseSerializer { - private val parsingMethodBindingRef = new AtomicReference[Map[Class[?], Method]](Map.empty) - private val toByteArrayMethodBindingRef = new AtomicReference[Map[Class[?], Method]](Map.empty) + private val parsingMethodBindingRef = new AtomicReference[Map[Class[?], MethodHandle]](Map.empty) + private val toByteArrayMethodBindingRef = new AtomicReference[Map[Class[?], MethodHandle]](Map.empty) private val allowedClassNames: Set[String] = { import scala.jdk.CollectionConverters._ @@ -75,24 +75,25 @@ class ProtobufSerializer(val system: ExtendedActorSystem) extends BaseSerializer manifest match { case Some(clazz) => @tailrec - def parsingMethod(method: Method = null): Method = { + def parsingMethod(method: java.lang.reflect.Method = null): MethodHandle = { val parsingMethodBinding = parsingMethodBindingRef.get() parsingMethodBinding.get(clazz) match { - case Some(cachedParsingMethod) => cachedParsingMethod + case Some(cachedParsingHandle) => cachedParsingHandle case None => checkAllowedClass(clazz) - val unCachedParsingMethod = + val unCachedMethod = if (method eq null) clazz.getDeclaredMethod("parseFrom", ProtobufSerializer.ARRAY_OF_BYTE_ARRAY: _*) else method + val unCachedHandle = MethodHandles.lookup().unreflect(unCachedMethod) if (parsingMethodBindingRef.compareAndSet( parsingMethodBinding, - parsingMethodBinding.updated(clazz, unCachedParsingMethod))) - unCachedParsingMethod + parsingMethodBinding.updated(clazz, unCachedHandle))) + unCachedHandle else - parsingMethod(unCachedParsingMethod) + parsingMethod(unCachedMethod) } } - parsingMethod().invoke(null, bytes) + parsingMethod().invoke(bytes).asInstanceOf[AnyRef] case None => throw new IllegalArgumentException("Need a protobuf message class to be able to serialize bytes using protobuf") @@ -102,20 +103,21 @@ class ProtobufSerializer(val system: ExtendedActorSystem) extends BaseSerializer override def toBinary(obj: AnyRef): Array[Byte] = { val clazz = obj.getClass @tailrec - def toByteArrayMethod(method: Method = null): Method = { + def toByteArrayMethod(method: java.lang.reflect.Method = null): MethodHandle = { val toByteArrayMethodBinding = toByteArrayMethodBindingRef.get() toByteArrayMethodBinding.get(clazz) match { - case Some(cachedtoByteArrayMethod) => cachedtoByteArrayMethod - case None => - val unCachedtoByteArrayMethod = + case Some(cachedHandle) => cachedHandle + case None => + val unCachedMethod = if (method eq null) clazz.getMethod("toByteArray") else method + val unCachedHandle = MethodHandles.lookup().unreflect(unCachedMethod) if (toByteArrayMethodBindingRef.compareAndSet( toByteArrayMethodBinding, - toByteArrayMethodBinding.updated(clazz, unCachedtoByteArrayMethod))) - unCachedtoByteArrayMethod + toByteArrayMethodBinding.updated(clazz, unCachedHandle))) + unCachedHandle else - toByteArrayMethod(unCachedtoByteArrayMethod) + toByteArrayMethod(unCachedMethod) } } toByteArrayMethod().invoke(obj).asInstanceOf[Array[Byte]] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
