`
JohnnyJian
  • 浏览: 106046 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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方法,其代码如下
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);
        }
    }
}

以上分析有不当之处敬请指出,谢谢大家的阅读。
2
4
分享到:
评论
1 楼 toby941 2013-09-05  
有办法不使用这种DGM机制么

相关推荐

    Groovy轻松入门——Grails实战基础篇

    ### Groovy轻松入门——Grails实战基础篇 #### 搭建Grails环境及创建Grails Demo程序 **Groovy**是一种面向对象的编程语言,它运行于Java平台上,能够与Java代码无缝集成。而**Grails**则是一款基于Groovy的高性能...

    groovy和Java相互调用1

    标题中的“Groovy和Java相互调用1”指的是在编程时如何在Groovy语言环境中调用Java类,以及反之,如何在Java程序中调用Groovy类。这是一种跨语言交互的方式,特别是在混合使用Groovy和Java的项目中非常常见。 ...

    Java调用Groovy,实时动态加载数据库groovy脚本

    本文将详细讲解如何在Java应用程序中调用Groovy脚本,实现从MongoDB数据库中读取并实时运行Groovy脚本,以及其背后的原理和优势。 首先,Groovy是一种与Java高度兼容的脚本语言,它可以无缝地与Java代码集成,共享...

    安卓Android源码——安卓调用百度地图,实现定位和搜索功能.zip

    这个压缩包文件“安卓Android源码——安卓调用百度地图,实现定位和搜索功能.zip”提供了示例代码,帮助开发者了解如何在Android应用中调用百度地图服务。以下是对这些功能的详细讲解: 首先,我们需要在项目中添加...

    Groovy need not rails——介绍自己写的一个基于groovy的框架,Webx

    通过深入学习和使用Webx,开发者可以充分利用Groovy的灵活性和生产力优势,同时享受到类似Rails的开发体验,而不必受限于Ruby语言。由于与Java平台的紧密集成,Webx还能够利用丰富的Java生态系统,为大型企业级应用...

    Java调用Groovy

    Java调用Groovy是一种常见的技术,特别是在开发过程中需要动态脚本支持时。Groovy是一种基于JVM(Java Virtual Machine)的、动态类型的编程语言,它与Java有着良好的互操作性,能够无缝集成到Java项目中。这篇博客...

    基于groovy实现 java脚本动态编译、部署、发布;可以通过脚本直接调用dubbo接口.zip

    本文将深入探讨如何基于Groovy实现Java脚本的动态编译、部署和发布,并且介绍如何通过Groovy脚本直接调用Dubbo接口,从而提高开发效率和灵活性。 Groovy是一种基于Java平台的强大的动态编程语言,它具有简洁的语法...

    groovy编写webservice服务端和客户端(含连接数据并输出JSON数据)

    - 对于博文链接中的源码,可以深入研究`wss.groovy`是如何启动和配置Web服务的,`WsTest.groovy`是如何测试服务的正确性,以及`MathService.groovy`和`DateFormatService.groovy`中具体的业务逻辑。 9. **最佳实践...

    Java中使用Groovy的三种方式

    本文将深入探讨在Java项目中使用Groovy的三种主要方式,并阐述它们各自的优势和应用场景。 一、作为嵌入式脚本 Java 6引入了JSR 223(Scripting for the Java Platform),允许在Java程序中直接执行脚本语言。...

    Show Your ToolBox——锋利的groovy

    《Show Your ToolBox——锋利的Groovy》 在IT领域,工具的选择和使用往往直接影响到工作效率和项目质量。本文将深入探讨Groovy这门强大的动态编程语言,它以其灵活性和与Java的紧密集成,成为了许多开发者的得力...

    实战Java虚拟机——JVM故障诊断与性能优化 pdf

    《实战Java虚拟机——JVM故障诊断与性能优化》内容简介:随着越来越多的第三方语言(Groovy、Scala、JRuby等)在Java虚拟机上运行,Java也俨然成为一个充满活力的生态圈。本书将通过200余示例详细介绍Java虚拟机中的...

    java调用脚本语言笔记(jython,jruby,groovy)

    本文将深入探讨如何在Java中调用三种流行的脚本语言:Jython、JRuby和Groovy。 ### Jython Jython是Python的一个Java实现,它允许Java开发者利用Python的强大语法和丰富的库。要使用Jython,首先需要在项目中引入...

    干货:Jenkins Pipeline调用shell、python、java、groovy脚本的正确使用姿势.doc

    Jenkins Pipeline 调用 shell、python、java、groovy 脚本的正确使用姿势 Jenkins Pipeline 是一个强大的自动化工具,可以帮助开发者自动化构建、测试和部署流程。在设计 Pipeline 脚本的过程中,经常会遇到调用...

    Groovy调用Weka生成决策树

    Groovy是一种基于JVM的动态编程语言,它与Java语法高度兼容,但提供了更简洁、灵活的语法。在数据挖掘领域,Weka是一款强大...在实际工作中,不断探索和优化模型参数,以及理解数据特性,将有助于提高模型的预测性能。

    深入探索Groovy脚本:文件操作的艺术

    本文将深入探讨如何使用Groovy脚本进行文件操作,包括文件的创建、读取、写入、删除等基本操作,以及更高级的操作,如文件过滤和搜索。通过实际的代码示例,我们将展示Groovy在文件操作中的优雅和力量。 Groovy提供...

    groovy调用java-se类库学习案例 Java学习资料

    Groovy是一种基于JVM的动态编程语言,它与Java有着紧密的联系,可以直接调用Java的类库。...通过阅读和运行这些示例,你将能够深入理解Groovy和Java之间的互操作性,并掌握在实际开发中如何有效利用这一特性。

    java与groovy互相调用1

    在Java和Groovy这两种语言中,互相调用代码是相当便捷的,因为它们可以无缝集成。Groovy是一种基于Java平台的脚本语言,它与Java有很高的互操作性,这使得开发者可以在同一个项目中混合使用Java和Groovy,充分利用...

    Groovy入门教程[参照].pdf

    3.兼容 Java:Groovy 可以与 Java 语言混合使用,Groovy 代码可以调用 Java 代码,反之亦然。 开发环境 1. JDK 1.5 以上 2. Eclipse + Groovy 插件(支持 Groovy 1.5.7) 创建 Groovy 项目 1. 新建一个 Java ...

    GMock——groovy下的mock工具

    **GMock:Groovy 下的 Mock 工具** 在软件开发中,单元测试是确保代码质量的重要环节。单元测试能够独立地验证代码的各个部分,确保它们按预期工作。为了进行有效的单元测试,经常会用到模拟对象(mock objects),...

Global site tag (gtag.js) - Google Analytics