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方法,其代码如下
public final Object invoke(Object object, Object[] arguments) {
try {
return cachedMethod.invoke(object, arguments); // cachedMethod的类型是Method
} catch (IllegalArgumentException e) {
throw new InvokerInvocationException(e);
} catch (IllegalAccessException e) {
throw new InvokerInvocationException(e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
throw (cause instanceof RuntimeException && !(cause instanceof MissingMethodException)) ? (RuntimeException) cause : new InvokerInvocationException(e);
}
}
可见,在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方法的包装类为例):
public class dgm$123 extends GeneratedMetaMethod {
...
public Object invoke(Object object, Object[] arguments) {
return DefaultGroovyMethods.each((Object) object, (Closure) arguments[0]); // 将各参数强制转换为对应的类型后,直接调用DefaultGroovyMethods中对应的方法
}
...
}
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方法生成包装类的:
public static void main(String[] args) throws IOException, ClassNotFoundException {
Class [] classes = new Class [] { // DGM方法事实上是分别位于这几个类中,而不只是在DefaultGroovyMethods类中
DefaultGroovyMethods.class,
SwingGroovyMethods.class,
SqlGroovyMethods.class,
XmlGroovyMethods.class,
EncodingGroovyMethods.class,
DateGroovyMethods.class,
ProcessGroovyMethods.class
};
List<CachedMethod> cachedMethodsList = new ArrayList<CachedMethod> ();
for (Class aClass : classes) {
Collections.addAll(cachedMethodsList, ReflectionCache.getCachedClass(aClass).getMethods()); // 获取这些类中的所有方法
}
final CachedMethod[] cachedMethods = cachedMethodsList.toArray(new CachedMethod[cachedMethodsList.size()]);
List<GeneratedMetaMethod.DgmMethodRecord> records = new ArrayList<GeneratedMetaMethod.DgmMethodRecord> ();
for (int i = 0, cur = 0; i < cachedMethods.length; i++) {
CachedMethod method = cachedMethods[i];
if (!method.isStatic() || !method.isPublic()) // DGM方法必须是static和public的
continue;
if (method.getCachedMethod().getAnnotation(Deprecated.class) != null) // 已过时的DGM方法不处理
continue;
if (method.getParameterTypes().length == 0) // DGM方法的第一个参数表示该方法应添加到哪个类上,因此DGM方法至少有一个参数
continue;
final Class returnType = method.getReturnType();
final String className = "org/codehaus/groovy/runtime/dgm$" + cur;
// record记录着DGM方法和包装类的对应关系
GeneratedMetaMethod.DgmMethodRecord record = new GeneratedMetaMethod.DgmMethodRecord();
records.add(record);
record.methodName = method.getName();
record.returnType = method.getReturnType();
record.parameters = method.getNativeParameterTypes();
record.className = className;
// 创建一个继承GeneratedMetaMethod的包装类
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_3,ACC_PUBLIC, className,null,"org/codehaus/groovy/reflection/GeneratedMetaMethod", null);
createConstructor(cw);
final String methodDescriptor = BytecodeHelper.getMethodDescriptor(returnType, method.getNativeParameterTypes());
createInvokeMethod(method, cw, returnType, methodDescriptor); // 我们只关注invoke方法的生成
createDoMethodInvokeMethod(method, cw, className, returnType, methodDescriptor);
createIsValidMethodMethod(method, cw, className);
cw.visitEnd();
// 将包装类写入class文件
final byte[] bytes = cw.toByteArray();
final FileOutputStream fileOutputStream = new FileOutputStream("target/classes/" + className + ".class");
fileOutputStream.write(bytes);
fileOutputStream.flush();
fileOutputStream.close();
cur++;
}
GeneratedMetaMethod.DgmMethodRecord.saveDgmInfo (records, "target/classes/META-INF/dgminfo"); // 将DGM方法和包装类的对应关系写入META-INF/dgminfo文件
}
我们再来看看用于创建包装类的invoke方法的createInvokeMethod方法:
private static void createInvokeMethod(CachedMethod method, ClassWriter cw, Class returnType, String methodDescriptor) {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null); // 创建一个public Object invoke(Object object, Object[] arguments)方法
mv.visitCode();
mv.visitVarInsn(ALOAD,1);
BytecodeHelper.doCast(mv, method.getParameterTypes()[0].getTheClass()); // 将调用者强制转换为DGM方法第一个参数的类型
loadParameters(method,2,mv);
mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(method.getDeclaringClass().getTheClass()), method.getName(), methodDescriptor); // 通过invokestatic指令直接调用DGM方法
BytecodeHelper.box(mv, returnType);
if (method.getReturnType() == void.class) {
mv.visitInsn(ACONST_NULL);
}
mv.visitInsn(ARETURN); // 返回值
mv.visitMaxs(0, 0);
mv.visitEnd();
}
protected static void loadParameters(CachedMethod method, int argumentIndex, MethodVisitor mv) {
CachedClass[] parameters = method.getParameterTypes();
int size = parameters.length-1;
for (int i = 0; i < size; i++) {
mv.visitVarInsn(ALOAD, argumentIndex);
BytecodeHelper.pushConstant(mv, i);
mv.visitInsn(AALOAD);
Class type = parameters[i+1].getTheClass();
BytecodeHelper.doCast(mv, type); // 将第i个参数强制转换为DGM方法中第i+1个参数的类型
}
}
MetaClassRegistryImpl在初始化的时候,会调用自身的registerMethods方法来从META-INF/dgminfo文件中读取DGM方法和包装类的对应关系:
private void registerMethods(final Class theClass, final boolean useMethodWrapper, final boolean useInstanceMethods, Map<CachedClass, List<MetaMethod>> map) {
if (useMethodWrapper) { // 我们只看useMethodWrapper为true的情况
try {
List<GeneratedMetaMethod.DgmMethodRecord> records = GeneratedMetaMethod.DgmMethodRecord.loadDgmInfo(); // 从META-INF/dgminfo文件中读取DGM方法和包装类的对应关系
for (GeneratedMetaMethod.DgmMethodRecord record : records) {
Class[] newParams = new Class[record.parameters.length - 1];
System.arraycopy(record.parameters, 1, newParams, 0, record.parameters.length-1);
MetaMethod method = new GeneratedMetaMethod.Proxy(
record.className,
record.methodName,
ReflectionCache.getCachedClass(record.parameters[0]),
record.returnType,
newParams
); // 为包装类创建GeneratedMetaMethod.Proxy代理
final CachedClass declClass = method.getDeclaringClass();
List<MetaMethod> arr = map.get(declClass);
if (arr == null) {
arr = new ArrayList<MetaMethod>(4);
map.put(declClass, arr);
}
arr.add(method);
instanceMethods.add(method);
}
} catch (Throwable e) {
...
} else {
...
}
}
最后来看看GeneratedMetaMethod.Proxy如何实现延迟加载包装类:
public static class Proxy extends GeneratedMetaMethod {
private volatile MetaMethod proxy;
private final String className;
...
public Object invoke(Object object, Object[] arguments) {
return proxy().invoke(object, arguments);
}
public final synchronized MetaMethod proxy() {
// 第一次调用时,通过createProxy创建包装类实例
if (proxy == null) {
createProxy();
}
return proxy;
}
private void createProxy() {
try {
// 载入包装类并进行实例化
Class<?> aClass = getClass().getClassLoader().loadClass(className.replace('/','.'));
Constructor<?> constructor = aClass.getConstructor(String.class, CachedClass.class, Class.class, Class[].class);
proxy = (MetaMethod) constructor.newInstance(getName(), getDeclaringClass(), getReturnType(), getNativeParameterTypes());
}
catch (Throwable t) {
t.printStackTrace();
throw new GroovyRuntimeException("Failed to create DGM method proxy : " + t, t);
}
}
}
以上分析有不当之处敬请指出,谢谢大家的阅读。
分享到:
相关推荐
0,下载Grails( http://dist.codehaus.org/grails/grails-bin-0.4.2.zip ,请留意朝花夕拾——Groovy & Grails中的“最新版本”提示)并解压到自己指定位置(我的位置是D:\D\MY_DEV\grails) 1,设置环境变量GRAILS...
Groovy 调用 Java 类groovy 调用 Java class 十分方便,只需要在类前导入该 Java 类,在 Groovy 代码中就可以无缝使用该
Java调用Groovy,实时动态加载数据库groovy脚本,java读取mongoDB的groovy脚本,加载实时运行,热部署
NULL 博文链接:https://key232323.iteye.com/blog/1336400
《实战Java虚拟机——JVM故障诊断与性能优化》内容简介:随着越来越多的第三方语言(Groovy、Scala、JRuby等)在Java虚拟机上运行,Java也俨然成为一个充满活力的生态圈。本书将通过200余示例详细介绍Java虚拟机中的...
干货:Jenkins Pipeline调用shell、python、java、groovy脚本的正确使用姿势.doc
Java中使用Groovy的三种方式,详细见我的博客。
《实战Java虚拟机——JVM故障诊断与性能优化》将通过200余示例详细介绍Java虚拟机中的各种参数配置、故障排查、性能监控以及性能优化。, 《实战Java虚拟机——JVM故障诊断与性能优化》共11章。第1~3章介绍了Java...
NULL 博文链接:https://yangwencan2002.iteye.com/blog/260697
groovy环境的mock工具,用于辅助单元测试.
概述主要介绍Java、spring与groovy结合使用,高清英文版本
Groovy介绍 什么是Groovy Groovy基本语法 Groovy正则 Java调用Groovy Groovy模板 Groovy应用
赠送jar包:groovy-3.0.9.jar; 赠送原API文档:groovy-3.0.9-javadoc.jar; 赠送源代码:groovy-3.0.9-sources.jar; 赠送Maven依赖信息文件:groovy-3.0.9.pom; 包含翻译后的API文档:groovy-3.0.9-javadoc-API...
NULL 博文链接:https://yinxvxv.iteye.com/blog/811328
groovy-se-study 本工程用于学习groovy和java-se的调用
}}编写一个 Groovy 类调用以上的 Java Beanclass GroovyCallJava { static void main(args) { Ja
apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望...
是Java语言扩展,因此可以与Java语言互相调用。在所有基于JVM虚拟机的语言中只有Scala可以媲美。使用Groovy可以快速灵活完成文本处理,数据库访问,XML处理等常见任务。研究表明,使用Groovy比使用Java写程序,代码量...
groovy请求方式,比较好的工具需要的自取,不积跬步无以至千里
Making Java Groovy is a practical handbook for developers who want to blend Groovy into their day to day work with Java It starts by introducing the key differences between Java and Groovy and how you...