Groovy深入探索——DGM调用优化
DGM调用优化是通过直接调用代替反射调用的方式来提高DGM方法的调用效率。
注:以下分析的Groovy源代码来自Groovy 1.8.0 rc4。
DGM
DGM其实是Groovy社区对DefaultGroovyMethods的简称,完整类名是org.codehaus.groovy.runtime.DefaultGroovyMethods。
DefaultGroovyMethods类中包含了Groovy为JDK的类添加的各种方法,如
反射调用和直接调用
在Groovy中,用于表示一个方法的是一个MetaMethod(这是一个抽象类)实例,MetaMethod类似于JDK中的Method。而在大多数情况下,这个表示方法的实例会是CachedMethod(MetaMethod的派生类)类型的。调用其代表的方法时,会调用CachedMethod的invoke方法,其代码如下
可见,在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方法的包装类为例):
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方法生成包装类的:
我们再来看看用于创建包装类的invoke方法的createInvokeMethod方法:
MetaClassRegistryImpl在初始化的时候,会调用自身的registerMethods方法来从META-INF/dgminfo文件中读取DGM方法和包装类的对应关系:
最后来看看GeneratedMetaMethod.Proxy如何实现延迟加载包装类:
以上分析有不当之处敬请指出,谢谢大家的阅读。
注:以下分析的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 }
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

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 }
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 }
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 }
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 }
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 }
以上分析有不当之处敬请指出,谢谢大家的阅读。