Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8340456: Reduce overhead of proxying Object methods in ProxyGenerator #21090

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,18 @@ final class ProxyGenerator {
private static final Method OBJECT_EQUALS_METHOD;
private static final Method OBJECT_TO_STRING_METHOD;

private static final String OBJECT_HASH_CODE_SIG;
private static final String OBJECT_EQUALS_SIG;
private static final String OBJECT_TO_STRING_SIG;

static {
try {
OBJECT_HASH_CODE_METHOD = Object.class.getMethod("hashCode");
OBJECT_HASH_CODE_SIG = OBJECT_HASH_CODE_METHOD.toShortSignature();
OBJECT_EQUALS_METHOD = Object.class.getMethod("equals", Object.class);
OBJECT_EQUALS_SIG = OBJECT_EQUALS_METHOD.toShortSignature();
OBJECT_TO_STRING_METHOD = Object.class.getMethod("toString");
OBJECT_TO_STRING_SIG = OBJECT_TO_STRING_METHOD.toShortSignature();
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
Expand Down Expand Up @@ -446,9 +453,9 @@ private byte[] generateClassFile() {
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/
addProxyMethod(new ProxyMethod(OBJECT_HASH_CODE_METHOD, "m0"));
addProxyMethod(new ProxyMethod(OBJECT_EQUALS_METHOD, "m1"));
addProxyMethod(new ProxyMethod(OBJECT_TO_STRING_METHOD, "m2"));
addProxyMethod(new ProxyMethod(OBJECT_HASH_CODE_METHOD, OBJECT_HASH_CODE_SIG, "m0"));
addProxyMethod(new ProxyMethod(OBJECT_EQUALS_METHOD, OBJECT_EQUALS_SIG, "m1"));
addProxyMethod(new ProxyMethod(OBJECT_TO_STRING_METHOD, OBJECT_TO_STRING_SIG, "m2"));

/*
* Accumulate all of the methods from the proxy interfaces.
Expand Down Expand Up @@ -526,7 +533,7 @@ private void addProxyMethod(Method m, Class<?> fromClass) {
return;
}
}
sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType,
sigmethods.add(new ProxyMethod(m, sig, returnType,
exceptionTypes, fromClass, "m" + proxyMethodCount++));
}

Expand Down Expand Up @@ -617,19 +624,19 @@ private void generateLookupAccessor(ClassBuilder clb) {
Label failLabel = cob.newLabel();
ClassEntry mhl = cp.classEntry(CD_MethodHandles_Lookup);
ClassEntry iae = cp.classEntry(CD_IllegalAccessException);
cob.aload(cob.parameterSlot(0))
cob.aload(0)
.invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("lookupClass", MTD_Class)))
.ldc(proxyCE)
.if_acmpne(failLabel)
.aload(cob.parameterSlot(0))
.aload(0)
.invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("hasFullPrivilegeAccess", MTD_boolean)))
.ifeq(failLabel)
.invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup)
.areturn()
.labelBinding(failLabel)
.new_(iae)
.dup()
.aload(cob.parameterSlot(0))
.aload(0)
.invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("toString", MTD_String)))
.invokespecial(cp.methodRefEntry(iae, exInit))
.athrow()
Expand All @@ -650,18 +657,16 @@ private class ProxyMethod {
private final Method method;
private final String shortSignature;
private final Class<?> fromClass;
private final Class<?>[] parameterTypes;
private final Class<?> returnType;
private final String methodFieldName;
private Class<?>[] exceptionTypes;
private final FieldRefEntry methodField;

private ProxyMethod(Method method, String sig, Class<?>[] parameterTypes,
private ProxyMethod(Method method, String sig,
Class<?> returnType, Class<?>[] exceptionTypes,
Class<?> fromClass, String methodFieldName) {
this.method = method;
this.shortSignature = sig;
this.parameterTypes = parameterTypes;
this.returnType = returnType;
this.exceptionTypes = exceptionTypes;
this.fromClass = fromClass;
Expand All @@ -670,32 +675,36 @@ private ProxyMethod(Method method, String sig, Class<?>[] parameterTypes,
cp.nameAndTypeEntry(methodFieldName, CD_Method));
}

private Class<?>[] parameterTypes() {
return method.getSharedParameterTypes();
}

/**
* Create a new specific ProxyMethod with a specific field name
*
* @param method The method for which to create a proxy
*/
private ProxyMethod(Method method, String methodFieldName) {
this(method, method.toShortSignature(),
method.getSharedParameterTypes(), method.getReturnType(),
private ProxyMethod(Method method, String sig, String methodFieldName) {
this(method, sig, method.getReturnType(),
method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName);
}

/**
* Generate this method, including the code and exception table entry.
*/
private void generateMethod(ClassBuilder clb) {
var desc = methodTypeDesc(returnType, parameterTypes);
var desc = methodTypeDesc(returnType, parameterTypes());
int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL
: ACC_PUBLIC | ACC_FINAL;
var catchList = computeUniqueCatchList(exceptionTypes);
clb.withMethod(method.getName(), desc, accessFlags, mb ->
mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes))))
.withCode(cob -> {
var catchList = computeUniqueCatchList(exceptionTypes);
cob.aload(cob.receiverSlot())
.getfield(handlerField)
.aload(cob.receiverSlot())
.getstatic(methodField);
Class<?>[] parameterTypes = parameterTypes();
if (parameterTypes.length > 0) {
// Create an array and fill with the parameters converting primitives to wrappers
cob.loadConstant(parameterTypes.length)
Expand Down Expand Up @@ -784,6 +793,7 @@ private void codeFieldInitialization(CodeBuilder cob) {
var cp = cob.constantPool();
codeClassForName(cob, fromClass);

Class<?>[] parameterTypes = parameterTypes();
cob.ldc(method.getName())
.loadConstant(parameterTypes.length)
.anewarray(classCE);
Expand Down Expand Up @@ -817,10 +827,14 @@ private void codeFieldInitialization(CodeBuilder cob) {
* loader is anticipated at local variable index 0.
*/
private void codeClassForName(CodeBuilder cob, Class<?> cl) {
cob.ldc(cl.getName())
.iconst_0() // false
.aload(0)// classLoader
.invokestatic(classForName);
if (cl == Object.class) {
cob.ldc(objectCE);
} else {
cob.ldc(cl.getName())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Class.forName is only necessary in a very small number of cases, namely when the overridden interface method has an unaccessible parameter type, usually a package-private type not accessible to the implementing class. Maybe we can always directly ldc if the class is public.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm just not sure what rules are quite right here. Wouldn't want to inadvertently regress this again, and not sure we have tests for all eventualities, so I started this off with the most conservative yet most beneficial improvement.

.iconst_0() // false
.aload(0)// classLoader
.invokestatic(classForName);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -26,7 +26,6 @@

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.CompilerControl;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
Expand All @@ -36,37 +35,29 @@
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

import org.openjdk.jmh.infra.Blackhole;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit;

/**
* Benchmark measuring java.lang.reflect.ProxyGenerator.generateProxyClass.
* It bypasses the cache of proxies to measure the time to construct a proxy.
*/
@Warmup(iterations = 5)
@Measurement(iterations = 10)
@Fork(value = 1)
@Warmup(iterations = 5, time = 2)
@Measurement(iterations = 5, time = 2)
@Fork(value = 1, jvmArgsPrepend = "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED")
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class ProxyPerf {
public class ProxyGeneratorBench {

/**
* Sample results from a Dell T7610.
* Benchmark Mode Cnt Score Error Units
* ProxyPerf.genIntf_1 avgt 10 35325.428 +/- 780.459 ns/op
* ProxyPerf.genIntf_1_V49 avgt 10 34309.423 +/- 727.188 ns/op
* ProxyPerf.genStringsIntf_3 avgt 10 46600.366 +/- 663.812 ns/op
* ProxyPerf.genStringsIntf_3_V49 avgt 10 45911.817 +/- 1598.536 ns/op
* ProxyPerf.genZeroParams avgt 10 33245.048 +/- 437.988 ns/op
* ProxyPerf.genZeroParams_V49 avgt 10 32954.254 +/- 1041.932 ns/op
* ProxyPerf.getPrimsIntf_2 avgt 10 43987.819 +/- 837.443 ns/op
* ProxyPerf.getPrimsIntf_2_V49 avgt 10 42863.462 +/- 1193.480 ns/op
* ProxyPerf.genPrimsIntf_2 avgt 10 43987.819 +/- 837.443 ns/op
*/

public interface Intf_1 {
Expand All @@ -84,79 +75,54 @@ public interface Intf_3 {
public String m2String(String s1, String s2);
}

private InvocationHandler handler;
private ClassLoader classloader;
private Method proxyGen;
private Method proxyGenV49;

@Setup
public void setup() {
try {
handler = (Object proxy, Method method, Object[] args) -> null;
classloader = ClassLoader.getSystemClassLoader();
Class<?> proxyGenClass = Class.forName("java.lang.reflect.ProxyGenerator");
proxyGen = proxyGenClass.getDeclaredMethod("generateProxyClass",
ClassLoader.class, String.class, java.util.List.class, int.class);
proxyGen.setAccessible(true);

// Init access to the old Proxy generator
Class<?> proxyGenClassV49 = Class.forName("java.lang.reflect.ProxyGenerator_v49");
proxyGenV49 = proxyGenClassV49.getDeclaredMethod("generateProxyClass",
String.class, java.util.List.class, int.class);
proxyGenV49.setAccessible(true);

} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException("ProxyClass setup fails", ex);
}
}

@Benchmark
public void genZeroParams(Blackhole bh) throws Exception {
public Object genZeroParams() throws Exception {
List<Class<?>> interfaces = List.of(Runnable.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1));
return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
}

@Benchmark
public void genIntf_1(Blackhole bh) throws Exception {
public Object genIntf_1() throws Exception {
List<Class<?>> interfaces = List.of(Intf_1.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1));
return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
}

@Benchmark
public void getPrimsIntf_2(Blackhole bh) throws Exception {
public Object genPrimsIntf_2() throws Exception {
List<Class<?>> interfaces = List.of(Intf_2.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1));
}
@Benchmark
public void genStringsIntf_3(Blackhole bh) throws Exception {
List<Class<?>> interfaces = List.of(Intf_3.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1));
return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
}

// Generate using the V49inal generator for comparison

@Benchmark
public void genZeroParams_V49(Blackhole bh) throws Exception {
List<Class<?>> interfaces = List.of(Runnable.class);
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1));
}

@Benchmark
public void genIntf_1_V49(Blackhole bh) throws Exception {
List<Class<?>> interfaces = List.of(Intf_1.class);
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1));
}

@Benchmark
public void getPrimsIntf_2_V49(Blackhole bh) throws Exception {
List<Class<?>> interfaces = List.of(Intf_2.class);
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1));
}
@Benchmark
public void genStringsIntf_3_V49(Blackhole bh) throws Exception {
public Object genStringsIntf_3() throws Exception {
List<Class<?>> interfaces = List.of(Intf_3.class);
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1));
return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
}

public static void main(String... args) throws Exception {
var benchmark = new ProxyGeneratorBench();
benchmark.setup();
benchmark.genZeroParams();
benchmark.genIntf_1();
benchmark.genPrimsIntf_2();
benchmark.genStringsIntf_3();
}
}