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
The following commit(s) were added to
refs/heads/refactor/replace-reflection-with-methodhandles by this push:
new ebdd3fb12e perf: cache MethodHandles and fix review issues
ebdd3fb12e is described below
commit ebdd3fb12e9241f1eb8417ce02686311ad871fe7
Author: 虎鸣 <[email protected]>
AuthorDate: Tue Jul 7 22:03:53 2026 +0800
perf: cache MethodHandles and fix review issues
Motivation:
Code review of PR #3300 identified several MethodHandle/VarHandle
instances that were re-created on every invocation, negating the
performance benefit of migrating from reflection. Additionally,
DnsSettings used publicLookup without fallback for --add-opens users.
Modification:
- VirtualThreadSupport: cache all MethodHandles/VarHandles as lazy vals
- LineNumbers: cache writeReplace MethodHandle per lambda class via
ConcurrentHashMap (matching Reflect.scala pattern)
- DnsSettings: add privateLookupIn fallback when publicLookup fails on
non-exported JDK-internal sun.net.dns package
- Reflect: replace non-idiomatic return with if/else expression
- BoxedType: remove unused primitiveForBoxed and toPrimitive
Result:
MethodHandle lookups execute once and are reused on subsequent calls.
DnsSettings works with both --add-exports and --add-opens configurations.
Dead code removed. Code style improved.
Tests:
Not run - targeted micro-optimizations to existing MethodHandle paths
References:
Refs #3300
---
.../pekko/dispatch/VirtualThreadSupport.scala | 63 +++++++++++++---------
.../org/apache/pekko/io/dns/DnsSettings.scala | 12 ++++-
.../scala/org/apache/pekko/util/BoxedType.scala | 2 -
.../scala/org/apache/pekko/util/LineNumbers.scala | 16 ++++--
.../main/scala/org/apache/pekko/util/Reflect.scala | 15 +++---
5 files changed, 68 insertions(+), 40 deletions(-)
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 9c37e96a9f..c7ea51466c 100644
--- a/actor/src/main/scala/org/apache/pekko/dispatch/VirtualThreadSupport.scala
+++ b/actor/src/main/scala/org/apache/pekko/dispatch/VirtualThreadSupport.scala
@@ -36,6 +36,38 @@ private[dispatch] object VirtualThreadSupport {
private val threadFactoryMethodType =
MethodType.methodType(classOf[ThreadFactory])
private val carrierThreadConstructorMethodType =
MethodType.methodType(Void.TYPE, classOf[ForkJoinPool])
+ // Cached method/var handles to avoid repeated lookups on every invocation
+ private lazy val newThreadPerTaskExecutorHandle = {
+ val executorsClazz =
ClassLoader.getSystemClassLoader.loadClass("java.util.concurrent.Executors")
+ lookup.findStatic(executorsClazz, "newThreadPerTaskExecutor",
newThreadPerTaskExecutorMethodType)
+ }
+
+ private lazy val threadBuilderClass =
ClassLoader.getSystemClassLoader.loadClass("java.lang.Thread$Builder")
+ private lazy val threadBuilderOfVirtualClass =
+
ClassLoader.getSystemClassLoader.loadClass("java.lang.Thread$Builder$OfVirtual")
+
+ private lazy val ofVirtualHandle =
+ lookup.findStatic(classOf[Thread], "ofVirtual",
MethodType.methodType(threadBuilderOfVirtualClass))
+
+ private lazy val ofVirtualNameHandle =
+ lookup.findVirtual(threadBuilderOfVirtualClass, "name",
+ MethodType.methodType(threadBuilderOfVirtualClass, classOf[String]))
+
+ private lazy val ofVirtualNameWithStartHandle =
+ lookup.findVirtual(threadBuilderOfVirtualClass, "name",
+ MethodType.methodType(threadBuilderOfVirtualClass, classOf[String],
java.lang.Long.TYPE))
+
+ private lazy val builderFactoryHandle =
+ lookup.findVirtual(threadBuilderClass, "factory", threadFactoryMethodType)
+
+ private lazy val virtualThreadClass =
+ ClassLoader.getSystemClassLoader.loadClass("java.lang.VirtualThread")
+
+ private lazy val defaultSchedulerHandle =
+ MethodHandles
+ .privateLookupIn(virtualThreadClass, privateLookup)
+ .findStaticVarHandle(virtualThreadClass, "DEFAULT_SCHEDULER",
classOf[ForkJoinPool])
+
/**
* Is virtual thread supported
*/
@@ -47,12 +79,7 @@ private[dispatch] object VirtualThreadSupport {
def newThreadPerTaskExecutor(threadFactory: ThreadFactory): ExecutorService
= {
require(threadFactory != null, "threadFactory should not be null.")
try {
- val executorsClazz =
ClassLoader.getSystemClassLoader.loadClass("java.util.concurrent.Executors")
- val newThreadPerTaskExecutorMethod = lookup.findStatic(
- executorsClazz,
- "newThreadPerTaskExecutor",
- newThreadPerTaskExecutorMethodType)
-
newThreadPerTaskExecutorMethod.invoke(threadFactory).asInstanceOf[ExecutorService]
+
newThreadPerTaskExecutorHandle.invoke(threadFactory).asInstanceOf[ExecutorService]
} catch {
case NonFatal(e) =>
// --add-opens java.base/java.lang=ALL-UNNAMED
@@ -89,19 +116,12 @@ private[dispatch] object VirtualThreadSupport {
require(isSupported, "Virtual thread is not supported.")
require(prefix != null && prefix.nonEmpty, "prefix should not be null or
empty.")
try {
- val builderClass =
ClassLoader.getSystemClassLoader.loadClass("java.lang.Thread$Builder")
- val ofVirtualClass =
ClassLoader.getSystemClassLoader.loadClass("java.lang.Thread$Builder$OfVirtual")
- val ofVirtualMethod = lookup.findStatic(classOf[Thread], "ofVirtual",
MethodType.methodType(ofVirtualClass))
- var builder = ofVirtualMethod.invoke()
+ var builder = ofVirtualHandle.invoke()
// set the name
if (start <= -1) {
- val nameMethod = lookup.findVirtual(ofVirtualClass, "name",
- MethodType.methodType(ofVirtualClass, classOf[String]))
- builder = nameMethod.invoke(builder, prefix + "-virtual-thread")
+ builder = ofVirtualNameHandle.invoke(builder, prefix +
"-virtual-thread")
} else {
- val nameMethod = lookup.findVirtual(ofVirtualClass, "name",
- MethodType.methodType(ofVirtualClass, classOf[String],
classOf[Long]))
- builder = nameMethod.invoke(builder, prefix + "-virtual-thread-", zero)
+ builder = ofVirtualNameWithStartHandle.invoke(builder, prefix +
"-virtual-thread-", zero)
}
// set the scheduler
if (executor ne null) {
@@ -111,8 +131,7 @@ private[dispatch] object VirtualThreadSupport {
classOf[Executor])
schedulerHandle.set(builder, executor)
}
- val factoryMethod = lookup.findVirtual(builderClass, "factory",
threadFactoryMethodType)
- factoryMethod.invoke(builder).asInstanceOf[ThreadFactory]
+ builderFactoryHandle.invoke(builder).asInstanceOf[ThreadFactory]
} catch {
case NonFatal(e) =>
// --add-opens java.base/java.lang=ALL-UNNAMED
@@ -139,13 +158,7 @@ private[dispatch] object VirtualThreadSupport {
def getVirtualThreadDefaultScheduler: ForkJoinPool =
try {
require(isSupported, "Virtual thread is not supported.")
- val clazz =
ClassLoader.getSystemClassLoader.loadClass("java.lang.VirtualThread")
- val fieldName = "DEFAULT_SCHEDULER"
- val schedulerHandle =
- MethodHandles
- .privateLookupIn(clazz, privateLookup)
- .findStaticVarHandle(clazz, fieldName, classOf[ForkJoinPool])
- schedulerHandle.get().asInstanceOf[ForkJoinPool]
+ defaultSchedulerHandle.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/io/dns/DnsSettings.scala
b/actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala
index 4a0778b18b..2f5489797c 100644
--- a/actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala
+++ b/actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala
@@ -172,6 +172,7 @@ object DnsSettings {
private final val DnsFallbackPort = 53
private val inetSocketAddress = """(.*?)(?::(\d+))?""".r
private val publicLookup = MethodHandles.publicLookup()
+ private val lookup = MethodHandles.lookup()
private val nameserversMethodType =
MethodType.methodType(classOf[util.List[?]])
/**
@@ -239,8 +240,15 @@ object DnsSettings {
def getNameserversUsingMethodHandles: Try[List[InetSocketAddress]] = {
system.dynamicAccess.getClassFor[Any]("sun.net.dns.ResolverConfiguration").flatMap
{ c =>
Try {
- val open = publicLookup.findStatic(c, "open",
MethodType.methodType(c))
- val nameservers = publicLookup.findVirtual(c, "nameservers",
nameserversMethodType)
+ val effectiveLookup: MethodHandles.Lookup =
+ try {
+ publicLookup.findStatic(c, "open", MethodType.methodType(c))
+ publicLookup
+ } catch {
+ case _: IllegalAccessException =>
MethodHandles.privateLookupIn(c, lookup)
+ }
+ val open = effectiveLookup.findStatic(c, "open",
MethodType.methodType(c))
+ val nameservers = effectiveLookup.findVirtual(c, "nameservers",
nameserversMethodType)
val instance = open.invoke()
val ns = nameservers.invoke(instance).asInstanceOf[util.List[String]]
val res = if (ns.isEmpty)
diff --git a/actor/src/main/scala/org/apache/pekko/util/BoxedType.scala
b/actor/src/main/scala/org/apache/pekko/util/BoxedType.scala
index fc55f5c0d6..0e7fc09754 100644
--- a/actor/src/main/scala/org/apache/pekko/util/BoxedType.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/BoxedType.scala
@@ -26,8 +26,6 @@ object BoxedType {
classOf[Float] -> classOf[jl.Float],
classOf[Double] -> classOf[jl.Double],
classOf[Unit] -> classOf[scala.runtime.BoxedUnit])
- private val toPrimitive = toBoxed.map(_.swap)
final def apply(c: Class[?]): Class[?] = if (c.isPrimitive) toBoxed(c) else c
- final def primitiveForBoxed(c: Class[?]): Option[Class[?]] =
toPrimitive.get(c)
}
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 dab9638700..0022a96520 100644
--- a/actor/src/main/scala/org/apache/pekko/util/LineNumbers.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/LineNumbers.scala
@@ -14,7 +14,8 @@
package org.apache.pekko.util
import java.io.{ DataInputStream, InputStream }
-import java.lang.invoke.{ MethodHandles, MethodType, SerializedLambda }
+import java.lang.invoke.{ MethodHandle, MethodHandles, MethodType,
SerializedLambda }
+import java.util.concurrent.ConcurrentHashMap
import scala.annotation.switch
import scala.util.control.NonFatal
@@ -32,6 +33,7 @@ object LineNumbers {
private val lookup = MethodHandles.lookup()
private val writeReplaceMethodType = MethodType.methodType(classOf[Object])
+ private val writeReplaceHandleCache = new ConcurrentHashMap[Class[?],
MethodHandle]
sealed trait Result
case object NoSourceInfo extends Result
@@ -210,10 +212,16 @@ object LineNumbers {
private def getStreamForLambda(l: AnyRef): Option[(InputStream,
Some[String])] =
try {
val c = l.getClass
+ val cached = writeReplaceHandleCache.get(c)
val writeReplaceHandle =
- MethodHandles
- .privateLookupIn(c, lookup)
- .findVirtual(c, "writeReplace", writeReplaceMethodType)
+ if (cached ne null) cached
+ else {
+ val handle = MethodHandles
+ .privateLookupIn(c, lookup)
+ .findVirtual(c, "writeReplace", writeReplaceMethodType)
+ val existing = writeReplaceHandleCache.putIfAbsent(c, handle)
+ if (existing eq null) handle else existing
+ }
writeReplaceHandle.invoke(l) match {
case serialized: SerializedLambda =>
if (debug)
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 4f23abf637..d0243c0846 100644
--- a/actor/src/main/scala/org/apache/pekko/util/Reflect.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/Reflect.scala
@@ -100,13 +100,14 @@ private[pekko] object Reflect {
val key = ConstructorKey(clazz, args.map(safeGetClass).toVector)
val cached = constructorHandles.get(key)
- if (cached ne null) return cached
-
- val selectedConstructorType =
- findConstructorMethodTypeFromClassMetadata(clazz,
args).getOrElse(error("no matching constructor"))
- val handle = findConstructorHandle(clazz, selectedConstructorType)
- val existing = constructorHandles.putIfAbsent(key, handle)
- if (existing eq null) handle else existing
+ if (cached ne null) cached
+ else {
+ val selectedConstructorType =
+ findConstructorMethodTypeFromClassMetadata(clazz,
args).getOrElse(error("no matching constructor"))
+ val handle = findConstructorHandle(clazz, selectedConstructorType)
+ val existing = constructorHandles.putIfAbsent(key, handle)
+ if (existing eq null) handle else existing
+ }
}
private def findConstructorMethodTypeFromClassMetadata[T](clazz: Class[T],
args: immutable.Seq[Any])
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]