摘要: groovy特性 Groovy是一门基于JVM的动态语言,同时也是一门面向对象的语言,语法上和Java非常相似。它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。 Java作为一种通用、静态类型的编译型语

groovy特性

Groovy是一门基于JVM的动态语言,同时也是一门面向对象的语言,语法上和Java非常相似。它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。

Java作为一种通用、静态类型的编译型语言有很多优势,但同样存在一些负担:

  • 重新编译太费工;
  • 静态类型不够灵活,重构起来时间可能比较长;
  • 部署的动静太大;
  • java的语法天然不适用生产dsl;

相对于Java,它在编写代码的灵活性上有非常明显的提升,对于一个长期使用Java的开发者来说,使用Groovy时能够明显地感受到负身上的“枷锁”轻了。Groovy是动态编译语言,广泛用作脚本语言和快速原型语言,主要优势之一就是它的生产力。Groovy 代码通常要比 Java 代码更容易编写,而且编写起来也更快,这使得它有足够的资格成为开发工作包中的一个附件。

Java不是解决动态层问题的理想语言,这些动态层问题包括原型设计、脚本处理等。可以把Groovy看作给Java静态世界补充动态能力的语言,同时Groovy已经实现了java不具备的语言特性:

  • 函数字面值;
  • 对集合的一等支持;
  • 对正则表达式的一等支持;
  • 对xml的一等支持;

groovy与java集成的方式

重温下Groovy调用Java方式,包括使用GroovyClassLoader、GroovyShell和GroovyScriptEngine。

GroovyClassLoader

用 Groovy 的 GroovyClassLoader ,动态地加载一个脚本并执行它的行为。GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类。

GroovyClassLoader loader = new GroovyClassLoader();
Class groovyClass = loader.parseClass(new File(groovyFileName));
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod("run", "helloworld");

GroovyShell

GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式,并最终通过GroovyShell返回Groovy表达式的计算结果。

GroovyShell shell = new GroovyShell();
Script groovyScript = shell.parse(new File(groovyFileName));
Object[] args = {};
groovyScript.invokeMethod("run", args);

GroovyScriptEngine

GroovyShell多用于推求对立的脚本或表达式,如果换成相互关联的多个脚本,使用GroovyScriptEngine会更好些。GroovyScriptEngine从您指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本,并且随着脚本变化而重新加载它们。如同GroovyShell一样,GroovyScriptEngine也允许您传入参数值,并能返回脚本的值。

Groovy代码文件与class文件的对应关系

而作为基于JVM的语言,Groovy可以非常容易的和Java进行互操作,但也需要编译成class文件后才能运行,所以了解Groovy代码文件和class文件的对应关系,有助于更好地理解Groovy的运行方式和结构。

对于没有任何类定义

如果Groovy脚本文件里只有执行代码,没有定义任何类(class),则编译器会生成一个Script的子类,类名和脚本文件的文件名一样,而脚本的代码会被包含在一个名为run的方法中,同时还会生成一个main方法,作为整个脚本的入口。

对于仅有一个类

如果Groovy脚本文件里仅含有一个类,而这个类的名字又和脚本文件的名字一致,这种情况下就和Java是一样的,即生成与所定义的类一致的class文件。

对于多个类

如果Groovy脚本文件含有多个类,groovy编译器会很乐意地为每个类生成一个对应的class文件。如果想直接执行这个脚本,则脚本里的第一个类必须有一个static的main方法。

groovy与java集成中经常出现的问题

使用GroovyShell的parse方法导致perm区爆满的问题

如果应用中内嵌Groovy引擎,会动态执行传入的表达式并返回执行结果,而Groovy每执行一次脚本,都会生成一个脚本对应的class对象,并new一个InnerLoader去加载这个对象,而InnerLoader和脚本对象都无法在gc的时候被回收运行一段时间后将perm占满,一直触发fullgc。

  • 为什么Groovy每执行一次脚本,都会生成一个脚本对应的class对象?

一个ClassLoader对于同一个名字的类只能加载一次,都由GroovyClassLoader加载,那么当一个脚本里定义了C这个类之后,另外一个脚本再定义一个C类的话,GroovyClassLoader就无法加载了。为什么这里会每次执行都会加载?

这是因为对于同一个groovy脚本,groovy执行引擎都会不同的命名,且命名与时间戳有关系。当传入text时,class对象的命名规则为:"script" + System.currentTimeMillis() + Math.abs(text.hashCode()) + ".groovy"。这就导致就算groovy脚本未发生任何变化,每次执行parse方法都会新生成一个脚本对应的class对象,且由GroovyClassLoader进行加载,不断增大perm区。

  • 为什么InnerLoader加载的对应无法通过gc清理掉?

大家都知道,JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载:1. 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例;2. 加载该类的ClassLoader已经被GC;3. 该类的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。

在GroovyClassLoader代码中有一个class对象的缓存,进一步跟下去,发现每次编译脚本时都会在Map中缓存这个对象,即:setClassCacheEntry(clazz)。每次groovy编译脚本后,都会缓存该脚本的Class对象,下次编译该脚本时,会优先从缓存中读取,这样节省掉编译的时间。这个缓存的Map由GroovyClassLoader持有,key是脚本的类名,这就导致每个脚本对应的class对象都存在引用,无法被gc清理掉。

  • 如何解决?

请参考:Groovy引发的PermGen区爆满问题定位与解决

如需更深入的理解GroovyClassLoader体系,请参考下面这篇文章Groovy深入探索——Groovy的ClassLoader体系

使用GroovyClassLoader加载机制导致频繁gc问题

通常使用如下代码在Java 中执行 Groovy 脚本:

GroovyClassLoader groovyLoader = new GroovyClassLoader();
Class<Script> groovyClass = (Class<Script>) groovyLoader.parseClass(groovyScript);
Script groovyScript = groovyClass.newInstance();

每次执行groovyLoader.parseClass(groovyScript),Groovy 为了保证每次执行的都是新的脚本内容,会每次生成一个新名字的Class文件,这个点已经在前文中说明过。当对同一段脚本每次都执行这个方法时,会导致的现象就是装载的Class会越来越多,从而导致PermGen被用满。
同时这里也存在性能瓶颈问题,如果去分析这段代码会发现90%的耗时占用在Class

为了避免这一问题通常做法是缓存Script对象,从而避免以上2个问题。在这过程中通常又会引入新的问题:

  • 高并发情况下,binding对象混乱导致计算出错

在高并发的情况下,在执行赋值binding对象后,真正执行run操作时,拿到的binding对象可能是其它线程赋值的对象,所以出现数据计算混乱的情况

  • 长时间运行仍然出现oom,无法解决Class

这点在上文中已经提到,由于groovyClassLoader会缓存每次编译groovy脚本的Class对象,下次编译该脚本时,会优先从缓存中读取,这样节省掉编译的时间。导致被加载的Class对象因为存在引用而无法被卸载,虽然通过缓存避免了短时间内大量生成新的class对象,但如果长时间运营仍然会存在问题。

比较好的做法是:

  • 每个 script 都 new 一个 GroovyClassLoader 来装载;
  • 对于 parseClass 后生成的 Class 对象进行cache,key 为 groovyScript 脚本的md5值。

CodeCache用满,导致JIT禁用问题

对于大量使用Groovy的应用,尤其是 Groovy 脚本还会经常更新的应用,由于这些Groovy脚本在执行了很多次后都会被JVM编译为 native 进行优化,会占据一些 CodeCache 空间,而如果这样的脚本很多的话,可能会导致 CodeCache 被用满,而 CodeCache 一旦被用满,JVM 的 Compiler 就会被禁用,那性能下降的就不是一点点了

Groovy与Java集成常见的坑的更多相关文章

  1. Groovy与Java集成常见的坑(转)

    groovy特性 Groovy是一门基于JVM的动态语言,同时也是一门面向对象的语言,语法上和Java非常相似.它结合了Python.Ruby和Smalltalk的许多强大的特性,Groovy 代码能 ...

  2. Java中常见的坑

    概述 Java是门极简风格的语言,比其它语言相比,它故意保持较少的特性,不仅在有些不常见的情况下会出些奇奇怪怪的错误,即使很一般的情况下也有可能让人栽根头.如果你习惯了别的语言,你读Java 的代码很 ...

  3. Java基础常见英语词汇

    Java基础常见英语词汇(共70个) ['ɔbdʒekt] ['ɔ:rientid]导向的                             ['prəʊɡræmɪŋ]编程 OO: object ...

  4. atitit groovy 总结java 提升效率

    atitit groovy 总结java 提升效率 #---环境配置 1 #------安装麻烦的 2 三.创建groovy项目 2 3.  添加 Groovy 类 2 4.  编译运行groovy类 ...

  5. Java中常见的5种WEB服务器介绍

    这篇文章主要介绍了Java中常见的5种WEB服务器介绍,它们分别是Tomcat.Resin.JBoss.WebSphere.WebLogic,需要的朋友可以参考下 Web服务器是运行及发布Web应用的 ...

  6. 纯Socket(BIO)长链接编程的常见的坑和填坑套路

    本文章纯属个人经验总结,伪代码也是写文章的时候顺便白板编码的,可能有逻辑问题,请帮忙指正,谢谢. Internet(全球互联网)是无数台机器基于TCP/IP协议族相互通信产生的.TCP/IP协议族分了 ...

  7. Groovy和Java互调

    Scala和Java为静态语言,Groovy为动态语言 Scala: 函数式编程,同时支持面向对象 Groovy: jvm上的脚本,较好兼容java语法,Groovy加强了Java集成. 可配置化的优 ...

  8. 使用yaml+groovy实现Java代码可配置化

    背景与目标 在使用函数接口和枚举实现配置式编程(Java与Scala实现),使用了函数接口和枚举实现了配置式编程.读者可先阅读此文,再来阅读本文. 有时,需要将一些业务逻辑,使用配置化的方式抽离出来, ...

  9. Java英文单词Java基础常见英语词汇

    Java英文单词Java基础常见英语词汇(共70个)                                                                          ...

随机推荐

  1. React文档(十一)提升state

    经常有些组件需要映射同一个改变的数据.我们建议将共用的state提升至最近的同一个祖先元素.我们来看看这是怎样运作的. 在这一节中,我们会创建一个温度计算器来计算提供的水温是否足够沸腾. 我们先创建一 ...

  2. Github如何创建|删除仓库

    一.创建 1.1.点击官网(https://github.com/),注册并登陆后点击Start a project或者点击网页右上角的加号选择New repository 1.2.网址自动变更为ht ...

  3. Tomcat 中 jsp 中文乱码显示处理解决方案

    原地址: http://blog.csdn.net/joyous/article/details/1504274 初学JSP,尤其是Tomcat环境(GlassFish默认UTF-8,则不存在此类问题 ...

  4. 20175227张雪莹 2018-2019-2 《Java程序设计》第六周学习总结

    20175227张雪莹 2018-2019-2 <Java程序设计>第六周学习总结 教材学习内容总结 第七章 内部类与异常类 内部类:在一个类中定义另一个类:包含内部类的类为外嵌类 内部类 ...

  5. wishhack 玩法概览

    抢流量玩法 超级店长玩法 https://www.wishhack.com/article/50.html https://www.wishhack.com/article/43.html

  6. Java的课堂实验

    题目是:用Aplet创建一个小程序,使得当你的鼠标经过图片时,放歌~ 其中,补充知识:1.MouseMotionListener这个和MouseListener这两个监听器要了解以下 2.Aplet这 ...

  7. guava-retrying 源码解析(停止策略详解)

    一.停止策略相关类 1.停止策略接口:StopStrategy接口,只有一个抽象方法 // 是否应该停止重试.不同的停止策略有不同的实现.boolean shouldStop(Attempt fail ...

  8. 虚拟机U盘挂载

    虚拟机中U盘挂载 一.连接U盘 虚拟机中    虚拟机→可移动设备→Syntek USB......(U盘的名称)→连接: 二.查看U盘的UUID “lsblk -f”: UUID为   35E6-9 ...

  9. Font 'C:\WINDOWS\FONTS\msyh.ttc' with 'Identity-H' is not recognized

    在生成PDF使用windows自带字体雅黑的时候不停的报“Font 'C:\WINDOWS\FONTS\msyh.ttc' with 'Identity-H' is not recognized”的错 ...

  10. 学习HashMap随笔(更新中)

    1.先来一个HashMap和HashTable的区别: HashMap线程不安全,键值可以为空 HashTable线程安全,键值不可以为空 2.hashmap我理解的是把数组存储和链表存储相结合了 具 ...