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]

Reply via email to