Replace java.lang.reflect with MethodHandle/VarHandle across core modules#3300
Open
He-Pin wants to merge 1 commit into
Open
Replace java.lang.reflect with MethodHandle/VarHandle across core modules#3300He-Pin wants to merge 1 commit into
He-Pin wants to merge 1 commit into
Conversation
pjfanning
reviewed
Jul 6, 2026
pjfanning
reviewed
Jul 6, 2026
pjfanning
reviewed
Jul 6, 2026
pjfanning
reviewed
Jul 6, 2026
pjfanning
reviewed
Jul 6, 2026
pjfanning
reviewed
Jul 6, 2026
Member
Author
|
This pR is not ready, let's mark it to draft for now |
He-Pin
added a commit
that referenced
this pull request
Jul 6, 2026
Motivation: PR #3300 replaced reflective constructor access with MethodHandles, but public JDK constructors in non-open modules were forced through privateLookupIn and failed under Scala 3 CI during throwable deserialization. Modification: Prefer MethodHandles.publicLookup for public constructor handles and fall back to privateLookupIn only when needed. Use constructor metadata for no-arg constructor checks and add a regression test for public JDK constructors. Result: TimeoutException deserializes back to its original type and constructor lookup avoids unnecessary deep access on public constructors. Tests: - rtk bash -lc export JAVA_HOME=$(/usr/libexec/java_home -v 17); export PATH=$JAVA_HOME/bin:$PATH; sbt ++3.3.8 actor-tests/Test/testOnly org.apache.pekko.util.ReflectSpec - passed, 11 tests - rtk bash -lc export JAVA_HOME=$(/usr/libexec/java_home -v 17); export PATH=$JAVA_HOME/bin:$PATH; sbt ++3.3.8 remote/Test/testOnly org.apache.pekko.remote.serialization.MiscMessageSerializerSpec - passed, 103 tests - rtk bash -lc export JAVA_HOME=$(/usr/libexec/java_home -v 17); export PATH=$JAVA_HOME/bin:$PATH; sbt ++3.3.8 actor-tests/Test/testOnly org.apache.pekko.actor.ActorCreationTest - passed, 7 tests - rtk scalafmt --mode diff-ref=origin/main - passed - rtk scalafmt --list --mode diff-ref=origin/main - passed - rtk git diff --check - passed References: Refs #3300
He-Pin
added a commit
that referenced
this pull request
Jul 6, 2026
…s 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; use Class.forName universally; prefer publicLookup for public constructors - VirtualThreadSupport: replace Field.set/get with VarHandle.set/get - ProtobufSerializer: replace Method-based caches with MethodHandle caches - LineNumbers: replace Method.invoke with MethodHandle.invoke - ByteBufferCleaner: replace Field.get(null) with VarHandle.get - ExtensionsImpl: replace Method.invoke(null) with MethodHandle.invoke - Replace java.lang.reflect with ClassGraph in test suites Result: JIT-inlinable field access and method invocation across all core reflection utilities, improving performance in actor creation, serialization, and virtual thread operations. Tests: - sbt ++3.3.8 actor-tests/Test/testOnly org.apache.pekko.util.ReflectSpec - sbt ++3.3.8 remote/Test/testOnly org.apache.pekko.remote.serialization.MiscMessageSerializerSpec - sbt ++3.3.8 actor-tests/Test/testOnly org.apache.pekko.actor.ActorCreationTest Refs #3300
f3c07c6 to
aa26fbe
Compare
pjfanning
reviewed
Jul 7, 2026
pjfanning
reviewed
Jul 7, 2026
pjfanning
reviewed
Jul 7, 2026
pjfanning
reviewed
Jul 7, 2026
pjfanning
reviewed
Jul 7, 2026
He-Pin
added a commit
that referenced
this pull request
Jul 7, 2026
Motivation: Address review feedback for PR #3300 and avoid replacing safe class loading and metadata reflection. Modification: Restore ordinary Class.forName and method metadata reflection where no deep reflective access is involved. Remove the ClassGraph test dependency and related package scanning. Cache MethodHandles lookups and fixed MethodType instances on the remaining MethodHandle paths. Result: Deep reflection remains handled by MethodHandle or VarHandle APIs while ordinary class loading keeps the original behavior and lower overhead. Tests: - rtk scalafmt --mode diff-ref=origin/main - rtk scalafmt --list --mode diff-ref=origin/main - sbt javafmtAll with JDK 17 - sbt javafmtCheckAll with JDK 17 - rtk git diff --check - focused actor, stream, remote, DNS, persistence, persistence-tck, cluster-sharding-typed, and cluster-sharding MultiJvm sbt validation - JDK21 virtual thread focused sbt validation References: Refs #3300
He-Pin
added a commit
that referenced
this pull request
Jul 7, 2026
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
He-Pin
added a commit
that referenced
this pull request
Jul 8, 2026
…re modules Motivation: java.lang.reflect (Method.invoke, Constructor.newInstance, Field.get/set) prevents JIT inlining. MethodHandle/VarHandle provide equivalent functionality with better performance characteristics. Modification: - Replace reflection with MethodHandle/VarHandle in ReflectiveDynamicAccess, Reflect, VirtualThreadSupport, ProtobufSerializer, LineNumbers, ByteBufferCleaner, and ExtensionsImpl - Cache MethodHandles.lookup() values to avoid repeated lookups - Remove unnecessary setAccessible(true) calls - Use invoke() for zero-arg method handles Result: Better JIT inlining opportunities across core modules. Tests: - sbt "actor / Test / compile" - sbt "actor-typed / Test / compile" References: Refs #3300
ebdd3fb to
062f859
Compare
He-Pin
added a commit
that referenced
this pull request
Jul 10, 2026
Motivation: Reduce repeated reflective lookup and per-call adaptation in actor creation, serialization, and JVM integration points while preserving behavior on modular JDKs. Modification: Cache pre-adapted MethodHandles and VarHandles at component or class lifetime, use exact invocation on hot paths, replace reflective array access with typed loops, and retain safe test migrations. Preserve JPMS caller lookup, class initialization, varargs, and exception behavior with focused regression tests. Result: Hot paths avoid repeated lookup, adaptation, and reflective array element access. Actor creation JMH showed no regression and a small non-conclusive improvement signal from 9.640 +/- 0.314 to 9.341 +/- 0.279 us/op. Tests: - JDK 17 focused actor reflection and dynamic access, protobuf serializer, file descriptor metrics, and typed extensions tests passed - Scala 3.3.8 core reflection and dynamic access, protobuf serializer, file descriptor metrics, and typed extensions tests passed - JDK 21 and JDK 25 focused reflection, protobuf, file descriptor metrics, and virtual-thread tests passed - sbt javafmtCheckAll +headerCheckAll checkCodeStyle passed - sbt +mimaReportBinaryIssues passed - git diff --check passed - scalafmt --list --mode diff-ref=origin/main passed - qodercli formal stdout review found no must-fix findings - sbt sortImports attempted; failed in unrelated modules with a scala-meta/scalafix NoSuchMethodError - sbt validatePullRequest started on JDK 17 and was stopped at explicit user request after compilation progressed without errors References: Refs #3300
062f859 to
0b2d049
Compare
He-Pin
added a commit
that referenced
this pull request
Jul 10, 2026
Motivation: Reduce repeated reflective lookup and per-call adaptation in actor creation, serialization, and JVM integration points while preserving reflection-compatible behavior on modular JDKs. Modification: Cache pre-adapted MethodHandles and VarHandles at component or class lifetime, use exact invocation on hot paths, and retain safe test migrations. Preserve JPMS caller lookup, class initialization, varargs, and exception behavior with focused regression tests. Result: Hot paths avoid repeated lookup and adaptation. Actor creation JMH showed no regression and a small non-conclusive improvement signal from 9.640 +/- 0.314 to 9.341 +/- 0.279 us/op. Tests: - JDK 17 focused actor reflection, dynamic access, PekkoException, protobuf serializer, and file descriptor metrics tests passed after the HashCode split: 36 tests - Earlier focused Scala 3.3.8, JDK 21, and JDK 25 reflection/virtual-thread validation passed on the same MethodHandle changes - sbt +headerCheckAll passed - scalafmt --list --mode diff-ref=origin/main passed - git diff --check passed - qodercli formal stdout review found no must-fix findings - GitHub Binary Compatibility, headers, code style, scalafmt, Scala 2.13/3/next tests, classic remoting, and Java 21 checks passed before the scope-only split References: Refs #3300
0b2d049 to
f3969ff
Compare
He-Pin
added a commit
that referenced
this pull request
Jul 10, 2026
Motivation: HashCode.hash iterates JVM arrays through java.lang.reflect.Array, adding reflective per-element access and boxing in a shared utility. Modification: Dispatch all nine JVM array kinds to typed while loops. Add a directional test that uses the original reflective algorithm as the parity oracle across primitive, reference, empty, null-element, and nested arrays. Add a JMH benchmark for primitive and reference arrays. Result: Hash values remain compatible while JDK 17 JMH measurements improve by roughly 39x to 135x for the measured 16- and 256-element arrays. Tests: - JDK 17 Scala 2.13 HashCodeSpec: 3 tests passed - JDK 17 Scala 3.3.8 HashCodeSpec: 3 tests passed - JDK 17 JMH: Int[16] 824.277 to 6.106 ns/op; Int[256] 12749.433 to 183.033 ns/op; AnyRef[16] 635.492 to 13.945 ns/op; AnyRef[256] 9491.604 to 240.387 ns/op - sbt +headerCheckAll and checkCodeStyle passed - scalafmt --list --mode diff-ref=origin/main passed - git diff --check passed - qodercli formal stdout review found no must-fix findings References: Refs #3300
Motivation: Reduce repeated reflective lookup and per-call adaptation in actor creation, serialization, and JVM integration points while preserving reflection-compatible behavior on modular JDKs. Modification: Cache pre-adapted MethodHandles and VarHandles at component or class lifetime, use exact invocation on hot paths, and retain safe test migrations. Preserve JPMS caller lookup, class initialization, varargs, and exception behavior with focused regression tests. Result: Hot paths avoid repeated lookup and adaptation. Actor creation JMH showed no regression and a small non-conclusive improvement signal from 9.640 +/- 0.314 to 9.341 +/- 0.279 us/op. Tests: - JDK 17 focused actor reflection, dynamic access, PekkoException, protobuf serializer, and file descriptor metrics tests passed after the HashCode split: 36 tests - Earlier focused Scala 3.3.8, JDK 21, and JDK 25 reflection/virtual-thread validation passed on the same MethodHandle changes - sbt +headerCheckAll passed - scalafmt --list --mode diff-ref=origin/main passed - git diff --check passed - qodercli formal stdout review found no must-fix findings - GitHub Binary Compatibility, headers, code style, scalafmt, Scala 2.13/3/next tests, classic remoting, and Java 21 checks passed before the scope-only split References: Refs #3300
f3969ff to
a3d8cf4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
java.lang.reflectmethod, constructor, and field access adds repeated lookup/adaptation overhead in actor creation, serialization, and JVM integration paths.Modification
ReflectiveDynamicAccess,Reflect, andIndirectActorProducer.VirtualThreadSupport,ProtobufSerializer,LineNumbers,DnsSettings,ByteBufferCleaner, and typedExtensionsImpl.HashCodearray-hashing optimization to Optimize JVM array hashing without reflection #3312.Result
Hot paths avoid repeated reflective lookup and per-call adaptation while preserving behavior on modular JDKs. Actor creation JMH showed no regression and a small non-conclusive signal from 9.640 +/- 0.314 to 9.341 +/- 0.279 us/op.
Tests
sbt "actor-tests / Test / testOnly org.apache.pekko.util.ReflectSpec org.apache.pekko.actor.DynamicAccessSpec org.apache.pekko.PekkoExceptionSpec"- passed, 26 tests after the splitsbt "remote / Test / testOnly org.apache.pekko.remote.serialization.ProtobufSerializerSpec"- passed, 9 tests after the splitsbt "testkit / Test / testOnly org.apache.pekko.testkit.metrics.FileDescriptorMetricSetSpec"- passed, 1 test after the splitsbt +headerCheckAll- passedscalafmt --list --mode diff-ref=origin/main- passedgit diff --check- passedqodercliformal stdout review - no must-fix findingsReferences
Refs #3300; independent HashCode optimization split to #3312