Skip to content

Commit e628358

Browse files
authored
Merge pull request openjdk#1 from plevart/proxy-default-method-performance
Performance improvements for Proxy::invokeDefaultMethod
2 parents c311a74 + bdb8a4f commit e628358

File tree

2 files changed

+173
-100
lines changed

2 files changed

+173
-100
lines changed

src/java.base/share/classes/java/lang/reflect/Proxy.java

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.util.concurrent.ConcurrentHashMap;
4848
import java.util.concurrent.atomic.AtomicInteger;
4949
import java.util.concurrent.atomic.AtomicLong;
50+
import java.util.function.BooleanSupplier;
5051

5152
import jdk.internal.access.JavaLangAccess;
5253
import jdk.internal.access.SharedSecrets;
@@ -55,6 +56,7 @@
5556
import jdk.internal.reflect.CallerSensitive;
5657
import jdk.internal.reflect.Reflection;
5758
import jdk.internal.loader.ClassLoaderValue;
59+
import jdk.internal.vm.annotation.Stable;
5860
import sun.reflect.misc.ReflectUtil;
5961
import sun.security.action.GetPropertyAction;
6062
import sun.security.util.SecurityConstants;
@@ -1154,6 +1156,8 @@ protected ConcurrentHashMap<Method, MethodHandle> computeValue(Class<?> type) {
11541156
}
11551157
};
11561158

1159+
private static final Object[] EMPTY_ARGS = new Object[0];
1160+
11571161
/**
11581162
* Invokes the specified default method on the given {@code proxy} instance with
11591163
* the given parameters. The given {@code method} must be a default method
@@ -1241,21 +1245,30 @@ protected ConcurrentHashMap<Method, MethodHandle> computeValue(Class<?> type) {
12411245
* <li>the given {@code method} is overridden directly or indirectly by
12421246
* the proxy interfaces and the method reference to the named
12431247
* method never resolves to the given {@code method}; or</li>
1244-
* <li>any of the given {@code args} does not match the parameter type of
1245-
* the default method to be invoked</li>
1248+
* <li>if length of given {@code args} array doesn't match the number of
1249+
* parameters of the method to be invoked or if {@code args} is null
1250+
* and the method to be invoked has parameters</li>
1251+
* <li>if any of the {@code args} elements can't be assigned to the
1252+
* boxed type of the corresponding method parameter or any of the
1253+
* {@code args} elements is null while the corresponding method
1254+
* parameter is of primitive type</li>
12461255
* </ul>
12471256
* @throws InvocationTargetException if the invoked default method throws
12481257
* any exception, it is wrapped by {@code InvocationTargetException}
12491258
* and rethrown
1250-
* @throws NullPointerException if {@code proxy} or {@code method} is
1251-
* {@code null}
1259+
* @throws NullPointerException if {@code proxy} or {@code method} is {@code null}
1260+
*
12521261
* @since 16
12531262
* @jvms 5.4.3. Method Resolution
12541263
*/
12551264
public static Object invokeDefaultMethod(Object proxy, Method method, Object... args)
12561265
throws InvocationTargetException {
12571266
Objects.requireNonNull(proxy);
12581267
Objects.requireNonNull(method);
1268+
if (args == null) {
1269+
// consistency with Method::invoke: null args array is equivalent to empty array
1270+
args = EMPTY_ARGS;
1271+
}
12591272

12601273
// verify that the object is actually a proxy instance
12611274
Class<?> proxyClass = proxy.getClass();
@@ -1270,61 +1283,63 @@ public static Object invokeDefaultMethod(Object proxy, Method method, Object...
12701283
ConcurrentHashMap<Method, MethodHandle> methods = DEFAULT_METHODS_MAP.get(proxyClass);
12711284
MethodHandle superMH = methods.get(method);
12721285

1273-
MethodType type = methodType(method.getReturnType(), method.getParameterTypes());
1274-
if (superMH != null) {
1275-
try {
1276-
// make sure that the method type matches
1277-
superMH.asType(type.insertParameterTypes(0, proxyClass));
1278-
} catch (WrongMethodTypeException e) {
1279-
throw new IllegalArgumentException(e.getMessage(), e);
1280-
}
1281-
} else {
1286+
if (superMH == null) {
1287+
MethodType type = methodType(method.getReturnType(), method.getParameterTypes());
1288+
MethodHandles.Lookup lookup = MethodHandles.lookup();
12821289
Class<?> proxyInterface = findProxyInterfaceOrElseThrow(proxyClass, method);
1290+
MethodHandle mh0;
12831291
try {
1284-
superMH = ((Proxy) proxy).proxyClassLookup(MethodHandles.lookup())
1285-
.findSpecial(proxyInterface, method.getName(), type, proxyClass)
1286-
.withVarargs(false);
1287-
} catch (IllegalAccessException|NoSuchMethodException e) {
1292+
mh0 = ((Proxy) proxy).proxyClassLookup(lookup)
1293+
.findSpecial(proxyInterface, method.getName(), type, proxyClass)
1294+
.withVarargs(false);
1295+
} catch (IllegalAccessException | NoSuchMethodException e) {
12881296
// should not reach here
12891297
throw new InternalError(e);
12901298
}
1299+
// this check can be turned into assertion as it is guaranteed to succeed by the virtue of
1300+
// looking up a default (instance) method declared or inherited by proxyInterface
1301+
// while proxyClass implements (is a subtype of) proxyInterface ...
1302+
assert ((BooleanSupplier) () -> {
1303+
try {
1304+
// make sure that the method type matches
1305+
mh0.asType(type.insertParameterTypes(0, proxyClass));
1306+
return true;
1307+
} catch (WrongMethodTypeException e) {
1308+
return false;
1309+
}
1310+
}).getAsBoolean() : "Wrong method type";
1311+
// change return type to Object
1312+
MethodHandle mh = mh0.asType(mh0.type().changeReturnType(Object.class));
1313+
// wrap any exception thrown with InvocationTargetException
1314+
mh = MethodHandles.catchException(mh, Throwable.class, wrapWithInvocationTargetExceptionMH());
1315+
// spread array of arguments among parameters (skipping 1st parameter - target)
1316+
mh = mh.asSpreader(1, Object[].class, type.parameterCount());
1317+
// change target type to Object
1318+
mh = mh.asType(MethodType.methodType(Object.class, Object.class, Object[].class));
1319+
12911320
// push MH into cache
1292-
MethodHandle cached = methods.putIfAbsent(method, superMH);
1321+
MethodHandle cached = methods.putIfAbsent(method, mh);
12931322
if (cached != null) {
12941323
superMH = cached;
1295-
}
1296-
}
1297-
1298-
// validate the arguments if they match the method type
1299-
int numArgs = args != null ? args.length : 0;
1300-
if (numArgs != type.parameterCount()) {
1301-
throw new IllegalArgumentException("args not matching the formal parameter types: " + type);
1302-
}
1303-
try {
1304-
if (numArgs > 0) {
1305-
Class<?>[] paramTypes = new Class<?>[numArgs];
1306-
for (int i = 0; i < numArgs; i++) {
1307-
Object o = args[i];
1308-
paramTypes[i] = o != null ? o.getClass() : type.parameterType(i);
1309-
}
1310-
superMH.asType(methodType(type.returnType(), proxyClass, paramTypes));
13111324
} else {
1312-
superMH.asType(methodType(type.returnType(), proxyClass));
1325+
superMH = mh;
13131326
}
1314-
} catch (WrongMethodTypeException e) {
1315-
throw new IllegalArgumentException(e);
13161327
}
13171328

13181329
// invoke the super method
13191330
try {
1320-
return superMH.asSpreader(1, Object[].class, type.parameterCount())
1321-
.invoke(proxy, args);
1322-
} catch (Throwable t) {
1323-
throw new InvocationTargetException(t);
1331+
return superMH.invokeExact(proxy, args);
1332+
} catch (ClassCastException | NullPointerException e) {
1333+
throw new IllegalArgumentException(e.getMessage(), e);
1334+
} catch (InvocationTargetException | RuntimeException | Error e) {
1335+
throw e;
1336+
} catch (Throwable e) {
1337+
// should not reach here
1338+
throw new InternalError(e);
13241339
}
13251340
}
13261341

1327-
/*
1342+
/**
13281343
* Finds the first proxy interface that declares the given method
13291344
* directly or indirectly.
13301345
*
@@ -1410,4 +1425,32 @@ public Lookup run() {
14101425
}
14111426
});
14121427
}
1428+
1429+
/**
1430+
* Wraps given cause with InvocationTargetException and throws it.
1431+
*
1432+
* @throws InvocationTargetException wrapping given cause
1433+
*/
1434+
private static Object wrapWithInvocationTargetException(Throwable cause) throws InvocationTargetException {
1435+
throw new InvocationTargetException(cause, cause.toString());
1436+
}
1437+
1438+
@Stable
1439+
private static MethodHandle wrapWithInvocationTargetExceptionMH;
1440+
1441+
private static MethodHandle wrapWithInvocationTargetExceptionMH() {
1442+
MethodHandle mh = wrapWithInvocationTargetExceptionMH;
1443+
if (mh == null) {
1444+
try {
1445+
wrapWithInvocationTargetExceptionMH = mh = MethodHandles.lookup().findStatic(
1446+
Proxy.class,
1447+
"wrapWithInvocationTargetException",
1448+
MethodType.methodType(Object.class, Throwable.class)
1449+
);
1450+
} catch (NoSuchMethodException | IllegalAccessException e) {
1451+
throw new InternalError(e);
1452+
}
1453+
}
1454+
return mh;
1455+
}
14131456
}

test/jdk/java/lang/reflect/Proxy/DefaultMethods.java

Lines changed: 88 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
* questions.
2222
*/
2323

24+
import java.io.IOException;
2425
import java.lang.reflect.InvocationHandler;
26+
import java.lang.reflect.InvocationTargetException;
2527
import java.lang.reflect.Method;
2628
import java.lang.reflect.Proxy;
2729
import java.util.Arrays;
@@ -87,6 +89,12 @@ default Object[] concat(Object first, Object... rest) {
8789
}
8890
}
8991

92+
public interface IX {
93+
default void doThrow(Throwable exception) throws Throwable {
94+
throw exception;
95+
}
96+
}
97+
9098
private static Method findDefaultMethod(Class<?> refc, Method m) {
9199
try {
92100
assertTrue(refc.isInterface());
@@ -113,15 +121,15 @@ public void test() {
113121
// a default method is declared in one of the proxy interfaces
114122
@DataProvider(name = "defaultMethods")
115123
private Object[][] defaultMethods() {
116-
return new Object[][] {
117-
new Object[] { new Class<?>[] { I1.class, I2.class }, true, 10 },
118-
new Object[] { new Class<?>[] { I1.class, I3.class }, true, 10 },
119-
new Object[] { new Class<?>[] { I1.class, I12.class }, true, 10 },
120-
new Object[] { new Class<?>[] { I2.class, I12.class }, true, 20 },
121-
new Object[] { new Class<?>[] { I4.class }, true, 40 },
122-
new Object[] { new Class<?>[] { I4.class, I3.class }, true, 40 },
123-
new Object[] { new Class<?>[] { I12.class }, false, -1 },
124-
new Object[] { new Class<?>[] { I12.class, I1.class, I2.class }, false, -1 },
124+
return new Object[][]{
125+
new Object[]{new Class<?>[]{I1.class, I2.class}, true, 10},
126+
new Object[]{new Class<?>[]{I1.class, I3.class}, true, 10},
127+
new Object[]{new Class<?>[]{I1.class, I12.class}, true, 10},
128+
new Object[]{new Class<?>[]{I2.class, I12.class}, true, 20},
129+
new Object[]{new Class<?>[]{I4.class}, true, 40},
130+
new Object[]{new Class<?>[]{I4.class, I3.class}, true, 40},
131+
new Object[]{new Class<?>[]{I12.class}, false, -1},
132+
new Object[]{new Class<?>[]{I12.class, I1.class, I2.class}, false, -1}
125133
};
126134
}
127135

@@ -155,28 +163,28 @@ public void testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected)
155163
// inherited from a superinterface of a proxy interface
156164
@DataProvider(name = "supers")
157165
private Object[][] supers() {
158-
return new Object[][] {
159-
// invoke "m" implemented in the first proxy interface
160-
// same as the method passed to InvocationHandler::invoke
161-
new Object[]{new Class<?>[]{ I1.class }, I1.class, 10},
162-
new Object[]{new Class<?>[]{ I2.class }, I2.class, 20},
163-
new Object[]{new Class<?>[]{ I1.class, I2.class }, I1.class, 10},
164-
// "m" is implemented in I2, an indirect superinterface of I3
165-
new Object[]{new Class<?>[]{ I3.class }, I3.class, 20},
166-
// "m" is implemented in I1, I2 and overridden in I4
167-
new Object[]{new Class<?>[]{ I4.class }, I4.class, 40},
168-
// invoke "m" implemented in the second proxy interface
169-
// different from the method passed to InvocationHandler::invoke
170-
new Object[]{new Class<?>[]{ I1.class, I2.class }, I2.class, 20},
171-
new Object[]{new Class<?>[]{ I1.class, I3.class }, I3.class, 20},
172-
// I2::m is implemented in more than one proxy interface directly or indirectly
173-
// I3::m resolves to I2::m (indirect superinterface)
174-
// I2 is the superinterface of I4 and I4 overrides m
175-
// the proxy class can invoke I4::m and I2::m
176-
new Object[]{new Class<?>[]{ I3.class, I4.class }, I3.class, 20},
177-
new Object[]{new Class<?>[]{ I3.class, I4.class }, I4.class, 40},
178-
new Object[]{new Class<?>[]{ I4.class, I3.class }, I3.class, 20},
179-
new Object[]{new Class<?>[]{ I4.class, I3.class }, I4.class, 40},
166+
return new Object[][]{
167+
// invoke "m" implemented in the first proxy interface
168+
// same as the method passed to InvocationHandler::invoke
169+
new Object[]{new Class<?>[]{I1.class}, I1.class, 10},
170+
new Object[]{new Class<?>[]{I2.class}, I2.class, 20},
171+
new Object[]{new Class<?>[]{I1.class, I2.class}, I1.class, 10},
172+
// "m" is implemented in I2, an indirect superinterface of I3
173+
new Object[]{new Class<?>[]{I3.class}, I3.class, 20},
174+
// "m" is implemented in I1, I2 and overridden in I4
175+
new Object[]{new Class<?>[]{I4.class}, I4.class, 40},
176+
// invoke "m" implemented in the second proxy interface
177+
// different from the method passed to InvocationHandler::invoke
178+
new Object[]{new Class<?>[]{I1.class, I2.class}, I2.class, 20},
179+
new Object[]{new Class<?>[]{I1.class, I3.class}, I3.class, 20},
180+
// I2::m is implemented in more than one proxy interface directly or indirectly
181+
// I3::m resolves to I2::m (indirect superinterface)
182+
// I2 is the superinterface of I4 and I4 overrides m
183+
// the proxy class can invoke I4::m and I2::m
184+
new Object[]{new Class<?>[]{I3.class, I4.class}, I3.class, 20},
185+
new Object[]{new Class<?>[]{I3.class, I4.class}, I4.class, 40},
186+
new Object[]{new Class<?>[]{I4.class, I3.class}, I3.class, 20},
187+
new Object[]{new Class<?>[]{I4.class, I3.class}, I4.class, 40}
180188
};
181189
}
182190

@@ -291,24 +299,24 @@ public void invokeNonProxyMethod() throws Exception {
291299
// negative cases
292300
@DataProvider(name = "negativeCases")
293301
private Object[][] negativeCases() {
294-
return new Object[][] {
295-
// I4::m overrides I1::m and I2::m
296-
new Object[] { new Class<?>[]{ I4.class }, I1.class, "m"},
297-
new Object[] { new Class<?>[]{ I4.class }, I2.class, "m"},
298-
// I12::m is not a default method
299-
new Object[] { new Class<?>[]{ I12.class }, I12.class, "m"},
300-
// non-proxy default method
301-
new Object[] { new Class<?>[]{ I3.class }, I1.class, "m"},
302-
// not a default method and not a proxy interface
303-
new Object[] { new Class<?>[]{ I12.class }, DefaultMethods.class, "test"},
304-
new Object[] { new Class<?>[]{ I12.class }, Runnable.class, "run"},
305-
// I2::privateMethod is a private method
306-
new Object[] { new Class<?>[]{ I3.class }, I2.class, "privateMethod"},
302+
return new Object[][]{
303+
// I4::m overrides I1::m and I2::m
304+
new Object[]{new Class<?>[]{I4.class}, I1.class, "m"},
305+
new Object[]{new Class<?>[]{I4.class}, I2.class, "m"},
306+
// I12::m is not a default method
307+
new Object[]{new Class<?>[]{I12.class}, I12.class, "m"},
308+
// non-proxy default method
309+
new Object[]{new Class<?>[]{I3.class}, I1.class, "m"},
310+
// not a default method and not a proxy interface
311+
new Object[]{new Class<?>[]{I12.class}, DefaultMethods.class, "test"},
312+
new Object[]{new Class<?>[]{I12.class}, Runnable.class, "run"},
313+
// I2::privateMethod is a private method
314+
new Object[]{new Class<?>[]{I3.class}, I2.class, "privateMethod"}
307315
};
308316
}
309317

310318
@Test(dataProvider = "negativeCases", expectedExceptions = {IllegalArgumentException.class})
311-
public void testIllegalArgument(Class<?>[] interfaces, Class<?> defc, String name)
319+
public void testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name)
312320
throws Exception {
313321
ClassLoader loader = DefaultMethods.class.getClassLoader();
314322
Object proxy = Proxy.newProxyInstance(loader, interfaces, HANDLER);
@@ -322,31 +330,53 @@ public void testIllegalArgument(Class<?>[] interfaces, Class<?> defc, String nam
322330
}
323331
}
324332

325-
@DataProvider(name = "arguments")
326-
private Object[][] arguments() {
333+
@DataProvider(name = "illegalArguments")
334+
private Object[][] illegalArguments() {
327335
return new Object[][] {
328-
new Object[] {},
329-
new Object[] { 100, "foo", 100 },
330-
new Object[] { "foo", 100},
336+
new Object[] {},
337+
new Object[] { 100 },
338+
new Object[] { 100, "foo", 100 },
339+
new Object[] { 100L, "foo" },
340+
new Object[] { "foo", 100},
341+
new Object[] { null, "foo" }
331342
};
332343
}
333344

334-
@Test(dataProvider = "arguments", expectedExceptions = {IllegalArgumentException.class})
335-
public void testWrongArguments(Object... args) throws Exception {
345+
@Test(dataProvider = "illegalArguments", expectedExceptions = {IllegalArgumentException.class})
346+
public void testIllegalArgument(Object... args) throws Exception {
336347
ClassLoader loader = DefaultMethods.class.getClassLoader();
337348
I4 proxy = (I4)Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
338349
Method m = I4.class.getMethod("mix", int.class, String.class);
339350
assertTrue(m.isDefault());
351+
if (args.length == 0) {
352+
// substitute empty args with null since @DataProvider doesn't allow null array
353+
args = null;
354+
}
340355
Proxy.invokeDefaultMethod(proxy, m, args);
341356
}
342357

343-
@Test(expectedExceptions = {IllegalArgumentException.class})
344-
public void testNullArguments() throws Exception {
358+
@DataProvider(name = "invocationTargetExceptions")
359+
private Object[][] invocationTargetExceptions() {
360+
return new Object[][] {
361+
new Object[] { new IOException() },
362+
new Object[] { new IllegalArgumentException() },
363+
new Object[] { new ClassCastException() },
364+
new Object[] { new NullPointerException() },
365+
new Object[] { new AssertionError() },
366+
new Object[] { new Throwable() }
367+
};
368+
}
369+
370+
@Test(dataProvider = "invocationTargetExceptions")
371+
public void testInvocationTargetException(Throwable exception) throws Exception {
345372
ClassLoader loader = DefaultMethods.class.getClassLoader();
346-
I4 proxy = (I4)Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
347-
Method m = I4.class.getMethod("mix", int.class, String.class);
348-
assertTrue(m.isDefault());
349-
Proxy.invokeDefaultMethod(proxy, m, (Object[])null);
373+
IX proxy = (IX)Proxy.newProxyInstance(loader, new Class<?>[]{IX.class}, HANDLER);
374+
Method m = IX.class.getMethod("doThrow", Throwable.class);
375+
try {
376+
Proxy.invokeDefaultMethod(proxy, m, exception);
377+
} catch (InvocationTargetException e) {
378+
assertEquals(e.getCause(), exception);
379+
}
350380
}
351381

352382
private static final InvocationHandler HANDLER = (proxy, method, params) -> {

0 commit comments

Comments
 (0)