Groovy深入探索——DGM调用优化

Groovy深入探索——DGM调用优化

DGM调用优化是通过直接调用代替反射调用的方式来提高DGM方法的调用效率。

注:以下分析的Groovy源代码来自Groovy 1.8.0 rc4。

DGM
DGM其实是Groovy社区对DefaultGroovyMethods的简称,完整类名是org.codehaus.groovy.runtime.DefaultGroovyMethods。
DefaultGroovyMethods类中包含了Groovy为JDK的类添加的各种方法,如
[ 1 2 3 ].each { println it }
中的each方法就是Groovy为Object类添加的方法,用于遍历对象中的所有元素。在Object类的实例上调用each方法,其实调用的是DefaultGroovyMethods类中的
public   static   < T >  T each(T self, Closure closure)

反射调用和直接调用
在Groovy中,用于表示一个方法的是一个MetaMethod(这是一个抽象类)实例,MetaMethod类似于JDK中的Method。而在大多数情况下,这个表示方法的实例会是CachedMethod(MetaMethod的派生类)类型的。调用其代表的方法时,会调用CachedMethod的invoke方法,其代码如下
 1  public   final  Object invoke(Object object, Object[] arguments) {
 2       try  {
 3           return  cachedMethod.invoke(object, arguments);  //  cachedMethod的类型是Method
 4      }  catch  (IllegalArgumentException e) {
 5           throw   new  InvokerInvocationException(e);
 6      }  catch  (IllegalAccessException e) {
 7           throw   new  InvokerInvocationException(e);
 8      }  catch  (InvocationTargetException e) {
 9          Throwable cause  =  e.getCause(); 
10           throw  (cause  instanceof  RuntimeException  &&   ! (cause  instanceof  MissingMethodException))  ?  (RuntimeException) cause :  new  InvokerInvocationException(e);
11      }
12  }

可见,在Groovy中一般是通过反射的方式调用方法的。
众所周知,在Java中,通过反射来调用方法,比直接调用方法会慢上几倍。因此,DGM调用优化的思想就是通过直接调用来代替反射调用。而要实现直接调用,则需要为DGM中的每个方法生成一个从MetaMethod派生的包装类,该类的invoke方法将直接调用DGM中对应的方法。

DGM调用优化
DGM调用优化的大体流程是这样的:

1. 在编译Groovy自身的Java代码(不是用Groovy写的代码)之后,通过调用DgmConverter类的main方法,为DefaultGroovyMethods的每个方法生成一个包装类,该类继承GeneratedMetaMethod类,而GeneratedMetaMethod类则继承MetaMethod类。该包装类的类名类似于org.codehaus.groovy.runtime.dgm$123($后跟一个数),在Groovy分发的jar包中可以找到总共918个这样的类,说明DGM中总共有918个方法。这些包装类的代码形式如下(以上面提到的each方法的包装类为例):
1  public   class  dgm$ 123   extends  GeneratedMetaMethod {
2      
3       public  Object invoke(Object object, Object[] arguments) {
4           return  DefaultGroovyMethods.each((Object) object, (Closure) arguments[ 0 ]);  //  将各参数强制转换为对应的类型后,直接调用DefaultGroovyMethods中对应的方法
5      }
6      
7  }

2. 通过调用GeneratedMetaMethod.DgmMethodRecord.saveDgmInfo方法,将哪个DGM方法对应哪个包装类的信息写入META-INF/dgminfo文件中

3. 当运行Groovy程序的时候,在MetaClassRegistryImpl的实例初始化时,通过调用GeneratedMetaMethod.DgmMethodRecord.loadDgmInfo方法,从META-INF/dgminfo文件中读取上一步写入的信息。然后为所有包装类创建GeneratedMetaMethod.Proxy实例作为其代理

4. 在Groovy程序第一次调用DGM方法时,则由GeneratedMetaMethod.Proxy实例载入对应的包装类并实例化,然后调用其invoke方法。这样就实现了包装类的延迟加载,在一定程度上加快了程序初始化加载的速度

那为什么不为用户写的Groovy程序的每一个方法,在运行时动态的创建这样直接调用的包装类,来提高每个方法的调用效率呢?那是因为这样会产生大量的类,由于每个类都是需要占用内存的,所以会对JVM用于存放类信息的PermGen内存区域造成压力,容易产生OutOfMemoryError。而DGM方法是Groovy程序中大量被调用的方法(即热点),而且数量只有不到1000个,因此适合为其生成包装类。

代码分析
先来看看DgmConverter是如何为DGM方法生成包装类的:
 1  public   static   void  main(String[] args)  throws  IOException, ClassNotFoundException {
 2      Class [] classes  =   new  Class [] {  //  DGM方法事实上是分别位于这几个类中,而不只是在DefaultGroovyMethods类中
 3          DefaultGroovyMethods. class ,
 4          SwingGroovyMethods. class ,
 5          SqlGroovyMethods. class ,
 6          XmlGroovyMethods. class ,
 7          EncodingGroovyMethods. class ,
 8          DateGroovyMethods. class ,
 9          ProcessGroovyMethods. class
10      };
11 
12      List < CachedMethod >  cachedMethodsList  =   new  ArrayList < CachedMethod >  ();
13       for  (Class aClass : classes) {
14          Collections.addAll(cachedMethodsList, ReflectionCache.getCachedClass(aClass).getMethods());  //  获取这些类中的所有方法
15      }
16       final  CachedMethod[] cachedMethods  =  cachedMethodsList.toArray( new  CachedMethod[cachedMethodsList.size()]);
17 
18      List < GeneratedMetaMethod.DgmMethodRecord >  records  =   new  ArrayList < GeneratedMetaMethod.DgmMethodRecord >  ();
19       for  ( int  i  =   0 , cur  =   0 ; i  <  cachedMethods.length; i ++ ) {
20          CachedMethod method  =  cachedMethods[i];
21           if  ( ! method.isStatic()  ||   ! method.isPublic())  //  DGM方法必须是static和public的
22             continue ;
23           if  (method.getCachedMethod().getAnnotation(Deprecated. class !=   null //  已过时的DGM方法不处理
24               continue ;
25           if  (method.getParameterTypes().length  ==   0 //  DGM方法的第一个参数表示该方法应添加到哪个类上,因此DGM方法至少有一个参数
26             continue ;
27 
28           final  Class returnType  =  method.getReturnType();
29           final  String className  =   " org/codehaus/groovy/runtime/dgm$ "   +  cur;
30 
31           //  record记录着DGM方法和包装类的对应关系
32          GeneratedMetaMethod.DgmMethodRecord record  =   new  GeneratedMetaMethod.DgmMethodRecord();
33          records.add(record);
34          record.methodName  =  method.getName();
35          record.returnType  =  method.getReturnType();
36          record.parameters  =  method.getNativeParameterTypes();
37          record.className   =  className;
38 
39           //  创建一个继承GeneratedMetaMethod的包装类
40          ClassWriter cw  =   new  ClassWriter(ClassWriter.COMPUTE_MAXS);
41          cw.visit(V1_3,ACC_PUBLIC, className, null , " org/codehaus/groovy/reflection/GeneratedMetaMethod " null );
42          createConstructor(cw);
43           final  String methodDescriptor  =  BytecodeHelper.getMethodDescriptor(returnType, method.getNativeParameterTypes());
44          createInvokeMethod(method, cw, returnType, methodDescriptor);  //  我们只关注invoke方法的生成
45          createDoMethodInvokeMethod(method, cw, className, returnType, methodDescriptor);
46          createIsValidMethodMethod(method, cw, className);
47          cw.visitEnd();
48 
49           //  将包装类写入class文件
50           final   byte [] bytes  =  cw.toByteArray();
51           final  FileOutputStream fileOutputStream  =   new  FileOutputStream( " target/classes/ "   +  className  +   " .class " );
52          fileOutputStream.write(bytes);
53          fileOutputStream.flush();
54          fileOutputStream.close();
55 
56          cur ++ ;
57      }
58 
59      GeneratedMetaMethod.DgmMethodRecord.saveDgmInfo (records,  " target/classes/META-INF/dgminfo " );  //  将DGM方法和包装类的对应关系写入META-INF/dgminfo文件
60  }

我们再来看看用于创建包装类的invoke方法的createInvokeMethod方法:
 1  private   static   void  createInvokeMethod(CachedMethod method, ClassWriter cw, Class returnType, String methodDescriptor) {
 2      MethodVisitor mv;
 3      mv  =  cw.visitMethod(ACC_PUBLIC,  " invoke " " (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; " null null );  //  创建一个public Object invoke(Object object, Object[] arguments)方法
 4      mv.visitCode();
 5      mv.visitVarInsn(ALOAD, 1 );
 6      BytecodeHelper.doCast(mv, method.getParameterTypes()[ 0 ].getTheClass());  //  将调用者强制转换为DGM方法第一个参数的类型
 7      loadParameters(method, 2 ,mv);
 8      mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(method.getDeclaringClass().getTheClass()), method.getName(), methodDescriptor);  //  通过invokestatic指令直接调用DGM方法
 9      BytecodeHelper.box(mv, returnType);
10       if  (method.getReturnType()  ==   void . class ) {
11          mv.visitInsn(ACONST_NULL);
12      }
13      mv.visitInsn(ARETURN);  //  返回值
14      mv.visitMaxs( 0 0 );
15      mv.visitEnd();
16  }
17 
18  protected   static   void  loadParameters(CachedMethod method,  int  argumentIndex, MethodVisitor mv) {
19      CachedClass[] parameters  =  method.getParameterTypes();
20       int  size  =  parameters.length - 1 ;
21       for  ( int  i  =   0 ; i  <  size; i ++ ) {
22          mv.visitVarInsn(ALOAD, argumentIndex);
23          BytecodeHelper.pushConstant(mv, i);
24          mv.visitInsn(AALOAD);
25          Class type  =  parameters[i + 1 ].getTheClass();
26          BytecodeHelper.doCast(mv, type);  //  将第i个参数强制转换为DGM方法中第i+1个参数的类型
27      }
28  }

MetaClassRegistryImpl在初始化的时候,会调用自身的registerMethods方法来从META-INF/dgminfo文件中读取DGM方法和包装类的对应关系:
 1  private   void  registerMethods( final  Class theClass,  final   boolean  useMethodWrapper,  final   boolean  useInstanceMethods, Map < CachedClass, List < MetaMethod >>  map) {
 2       if  (useMethodWrapper) {  //  我们只看useMethodWrapper为true的情况
 3           try  {
 4              List < GeneratedMetaMethod.DgmMethodRecord >  records  =  GeneratedMetaMethod.DgmMethodRecord.loadDgmInfo();  //  从META-INF/dgminfo文件中读取DGM方法和包装类的对应关系
 5 
 6               for  (GeneratedMetaMethod.DgmMethodRecord record : records) {
 7                  Class[] newParams  =   new  Class[record.parameters.length  -   1 ];
 8                  System.arraycopy(record.parameters,  1 , newParams,  0 , record.parameters.length - 1 );
 9 
10                  MetaMethod method  =   new  GeneratedMetaMethod.Proxy(
11                          record.className,
12                          record.methodName,
13                          ReflectionCache.getCachedClass(record.parameters[ 0 ]),
14                          record.returnType,
15                          newParams
16                  );  //  为包装类创建GeneratedMetaMethod.Proxy代理
17                   final  CachedClass declClass  =  method.getDeclaringClass();
18                  List < MetaMethod >  arr  =  map.get(declClass);
19                   if  (arr  ==   null ) {
20                      arr  =   new  ArrayList < MetaMethod > ( 4 );
21                      map.put(declClass, arr);
22                  }
23                  arr.add(method);
24                  instanceMethods.add(method);
25              }
26          }  catch  (Throwable e) {
27              
28      }  else  {
29          
30      }
31  }

最后来看看GeneratedMetaMethod.Proxy如何实现延迟加载包装类:
 1  public   static   class  Proxy  extends  GeneratedMetaMethod {
 2       private   volatile  MetaMethod proxy;
 3       private   final  String className;
 4      
 5       public  Object invoke(Object object, Object[] arguments) {
 6           return  proxy().invoke(object, arguments);
 7      }
 8 
 9       public   final   synchronized  MetaMethod proxy() {
10           //  第一次调用时,通过createProxy创建包装类实例
11           if  (proxy  ==   null ) {
12              createProxy();
13          }
14           return  proxy;
15      }
16 
17       private   void  createProxy() {
18           try  {
19               //  载入包装类并进行实例化
20              Class <?>  aClass  =  getClass().getClassLoader().loadClass(className.replace( ' / ' , ' . ' ));
21              Constructor <?>  constructor  =  aClass.getConstructor(String. class , CachedClass. class , Class. class , Class[]. class );
22              proxy  =  (MetaMethod) constructor.newInstance(getName(), getDeclaringClass(), getReturnType(), getNativeParameterTypes());
23          }
24           catch  (Throwable t) {
25              t.printStackTrace();
26               throw   new  GroovyRuntimeException( " Failed to create DGM method proxy :  "   +  t, t);
27          }
28      }
29  }

以上分析有不当之处敬请指出,谢谢大家的阅读。

你可能感兴趣的:(Groovy深入探索——DGM调用优化)