Looking for Sponsor: JDK-8229959 Convert proxy class to use constant dynamic

Johannes Kuhn info at j-kuhn.de
Fri Nov 22 21:27:11 UTC 2019


On 22.11.2019 20:52, Brian Goetz wrote:
> For those that are not intimately familiar with the mechanics of proxy 
> class generation, it would be great to flesh this out with a code 
> example, that shows the proposed old and new translation. This would 
> make it easier for folks to evaluate the benefits and costs of the 
> approach, and possibly suggest improvements.
>
> On 11/21/2019 10:23 PM, Johannes Kuhn wrote:
>>
>> * What benefits would such a change have?
>> First, it allows us to drop the static final fields and the class 
>> initializer for the generated proxy classes.
>> Second, it allows the code (both generated and generator) to be more 
>> clear by moving the essential parts together.
>> Third, it might bring some performance improvement, because the 
>> j.l.r.Method lookup is delayed to it's first use - which in some 
>> cases might never happen.
>
Thanks for the suggestion. A proxy can be used to create an 
implementation of one or more interfaces at run time.
I just let Java 13 dump a generated Proxy and decompiled it using JD. 
This is the result:

packagecom.sun.proxy;
//imports...;
publicfinalclass$Proxy0extendsProxyimplementsRunnable{
privatestaticMethodm1;
privatestaticMethodm3;
privatestaticMethodm2;
privatestaticMethodm0;

public$Proxy0(InvocationHandlerparamInvocationHandler){super(paramInvocationHandler);}

publicfinalbooleanequals(ObjectparamObject){
try{
return((Boolean)this.h.invoke(this,m1,newObject[]{paramObject})).booleanValue();
}catch(Error|RuntimeExceptionerror){
throwerror;
}catch(Throwablethrowable){
thrownewUndeclaredThrowableException(throwable);
}
}

publicfinalvoidrun(){
try{
this.h.invoke(this,m3,null);
return;
}catch(Error|RuntimeExceptionerror){
throwerror;
}catch(Throwablethrowable){
thrownewUndeclaredThrowableException(throwable);
}
}

publicfinalStringtoString(){
try{
return(String)this.h.invoke(this,m2,null);
}catch(Error|RuntimeExceptionerror){
throwerror;
}catch(Throwablethrowable){
thrownewUndeclaredThrowableException(throwable);
}
}

publicfinalinthashCode(){
try{
return((Integer)this.h.invoke(this,m0,null)).intValue();
}catch(Error|RuntimeExceptionerror){
throwerror;
}catch(Throwablethrowable){
thrownewUndeclaredThrowableException(throwable);
}
}

static{
try{
m1=Class.forName("java.lang.Object").getMethod("equals",newClass[]{Class.forName("java.lang.Object")});
m3=Class.forName("java.lang.Runnable").getMethod("run",newClass[0]);
m2=Class.forName("java.lang.Object").getMethod("toString",newClass[0]);
m0=Class.forName("java.lang.Object").getMethod("hashCode",newClass[0]);
return;
}catch(NoSuchMethodExceptionnoSuchMethodException){
thrownewNoSuchMethodError(noSuchMethodException.getMessage());
}catch(ClassNotFoundExceptionclassNotFoundException){
thrownewNoClassDefFoundError(classNotFoundException.getMessage());
}
}
}

The new implementation using ASM generates a similar class.
I intend to replace the the references to m0, m1... in the generated 
code through an LDC instruction, and remove the code that generates the 
fields and the static class initializer.

The resulting code would look a something like this:

packagecom.sun.proxy;
//imports...;
publicfinalclass$Proxy0extendsProxyimplementsRunnable{

public$Proxy0(InvocationHandlerparamInvocationHandler){super(paramInvocationHandler);}

publicfinalbooleanequals(ObjectparamObject){
try{
return((Boolean)this.h.invoke(this,__LDC(__MethodHandle 
(com.sun.proxy.$Proxy0::$bootstap, "_", Method.class, 
__MethodHandle(java.lang.Object::equals)),newObject[]{paramObject})).booleanValue();
}catch(Error|RuntimeExceptionerror){
throwerror;
}catch(Throwablethrowable){
thrownewUndeclaredThrowableException(throwable);
}
}

// repeat for run(), hashCode() and equals(Object other)
private static final Method $bootstrap(Lookup lookup, String ignored, 
Class<?> clazz, MethodHandle mh) { return 
lookup.revealDirect(mh).reflectAs(clazz, lookup); }
}

I used __LDC as placeholder for the LDC bytecode instruction (limited to 
constant dynamic), and __MethodHandle for a MethodHandle in the constant 
pool.
This means the Method is only looked up when the proxy method is called 
for the first time.

In the actual source code (the generating code), it means that the 
following methods can be dropped:
ProxyGenerator.generateStaticInitializer() 
https://hg.openjdk.java.net/jdk/jdk/file/a2a921609481/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java#l576
ProxyGenerator.ProxyMethod.codeFieldInitialization(MethodVisitor, 
String) 
https://hg.openjdk.java.net/jdk/jdk/file/a2a921609481/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java#l576

It will require the addition of a new method, which creates the 
$bootstrap method, and a change 
ProxyGenerator.ProxyMethod.generateMethod to emit an LDC instruction 
instead of a GETSTATIC.

The next step is to remove the now unused ProxyMethod.methodFieldName 
and it's dependencies (constructor, their calls...), but this should be 
a fairly natural change.
After all those changes, I assume that I will delete more code than I 
will add and change.

Also, as suggested in my last mail, the bootstrap method doesn't need to 
be in the generated proxy class, it just needs to be accessible from the 
generated proxy classes (which can be in any module).
Remi Forax suggested to put it in the 
java.lang.invoke.ConstantBootstraps in his initial proposal[1] - but 
this means it would be a public API.
Using a private method avoids that, at some low cost, and changing to an 
external bootstrap method later would be a small change.

[1]: 
https://mail.openjdk.java.net/pipermail/core-libs-dev/2019-August/061923.html




More information about the core-libs-dev mailing list