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.
1、GroovyClassLoader
用 Groovy 的 GroovyClassLoader ,动态地加载一个脚本并执行它的行为。GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类。
2、GroovyShell
GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式,并最终通过GroovyShell返回Groovy表达式的计算结果。
3、GroovyScriptEngine
GroovyShell多用于推求对立的脚本或表达式,如果换成相互关联的多个脚本,使用GroovyScriptEngine会更好些。GroovyScriptEngine从您指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本,并且随着脚本变化而重新加载它们。如同GroovyShell一样,GroovyScriptEngine也允许您传入参数值,并能返回脚本的值。
二、Groovy代码文件与class文件的对应关系
作为基于JVM的语言,Groovy可以非常容易的和Java进行互操作,但也需要编译成class文件后才能运行,所以了解Groovy代码文件和class文件的对应关系,有助于更好地理解Groovy的运行方式和结构。
1、对于没有任何类定义
如果Groovy脚本文件里只有执行代码,没有定义任何类(class),则编译器会生成一个Script的子类,类名和脚本文件的文件名一样,而脚本的代码会被包含在一个名为run的方法中,同时还会生成一个main方法,作为整个脚本的入口。
2、对于仅有一个类
如果Groovy脚本文件里仅含有一个类,而这个类的名字又和脚本文件的名字一致,这种情况下就和Java是一样的,即生成与所定义的类一致的class文件。
3、对于多个类
如果Groovy脚本文件含有多个类,groovy编译器会很乐意地为每个类生成一个对应的class文件。如果想直接执行这个脚本,则脚本里的第一个类必须有一个static的main方法。
三、Groovy与Java集成中经常出现的问题
1、使用GroovyShell的parse方法导致perm区爆满的问题
如果应用中内嵌Groovy引擎,会动态执行传入的表达式并返回执行结果,而Groovy每执行一次脚本,都会生成一个脚本对应的class对象,并new一个InnerLoader去加载这个对象,而InnerLoader和脚本对象都无法在gc的时候被回收运行一段时间后将perm占满,一直触发fullgc。
A.为什么Groovy每执行一次脚本,都会生成一个脚本对应的class对象?
一个ClassLoader对于同一个名字的类只能加载一次,都由GroovyClassLoader加载,那么当一个脚本里定义了C这个类之后,另外一个脚本再定义一个C类的话,GroovyClassLoader就无法加载了。为什么这里会每次执行都会加载?
这是因为对于同一个groovy脚本,groovy执行引擎都会不同的命名,且命名与时间戳有关系。当传入text时,class对象的命名规则为:"script" + System.currentTimeMillis() + Math.abs(text.hashCode()) + ".groovy"。这就导致就算groovy脚本未发生任何变化,每次执行parse方法都会新生成一个脚本对应的class对象,且由GroovyClassLoader进行加载,不断增大perm区。
B.为什么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清理掉。
2、使用GroovyClassLoader加载机制导致频繁gc问题
通常使用如下代码在Java 中执行 Groovy 脚本:
每次执行groovyLoader.parseClass(groovyScript),Groovy 为了保证每次执行的都是新的脚本内容,会每次生成一个新名字的Class文件,这个点已经在前文中说明过。当对同一段脚本每次都执行这个方法时,会导致的现象就是装载的Class会越来越多,从而导致PermGen被用满。 同时这里也存在性能瓶颈问题,如果去分析这段代码会发现90%的耗时占用在Class
为了避免这一问题通常做法是缓存Script对象,从而避免以上2个问题。在这过程中通常又会引入新的问题:
A.高并发情况下,binding对象混乱导致计算出错
在高并发的情况下,在执行赋值binding对象后,真正执行run操作时,拿到的binding对象可能是其它线程赋值的对象,所以出现数据计算混乱的情况
B.长时间运行仍然出现oom,无法解决Class
这点在上文中已经提到,由于groovyClassLoader会缓存每次编译groovy脚本的Class对象,下次编译该脚本时,会优先从缓存中读取,这样节省掉编译的时间。导致被加载的Class对象因为存在引用而无法被卸载,虽然通过缓存避免了短时间内大量生成新的class对象,但如果长时间运营仍然会存在问题。
比较好的做法是:
- 每个 script 都 new 一个 GroovyClassLoader 来装载;
- 对于 parseClass 后生成的 Class 对象进行cache,key 为 groovyScript 脚本的md5值
3、CodeCache用满,导致JIT禁用问题
对于大量使用Groovy的应用,尤其是 Groovy 脚本还会经常更新的应用,由于这些Groovy脚本在执行了很多次后都会被JVM编译为 native 进行优化,会占据一些 CodeCache 空间,而如果这样的脚本很多的话,可能会导致 CodeCache 被用满,而 CodeCache 一旦被用满,JVM 的 Compiler 就会被禁用,那性能下降的就不是一点点了
Groovy选型的更多相关文章
- Java与groovy混编 —— 一种兼顾接口清晰和实现敏捷的开发方式
有大量平均水平左右的"工人"可被选择.参与进来 -- 这意味着好招人 有成熟的.大量的程序库可供选择 -- 这意味着大多数项目都是既有程序库的拼装,标准化程度高而定制化场景少 开发 ...
- 2020 年了,Java 日志框架到底哪个性能好?——技术选型篇
大家好,之前写(shui)了两篇其他类型的文章,感觉大家反响不是很好,于是我乖乖的回来更新硬核技术文了. 经过本系列前两篇文章我们了解到日志框架大战随着 SLF4j 的一统天下而落下帷幕,但 SLF4 ...
- 复杂多变场景下的Groovy脚本引擎实战
一.前言 因为之前在项目中使用了Groovy对业务能力进行一些扩展,效果比较好,所以简单记录分享一下,这里你可以了解: 为什么选用Groovy作为脚本引擎 了解Groovy的基本原理和Java如何集成 ...
- 快速web开发中的前后端框架选型最佳实践
这个最佳实践是我目前人在做的一个站点,主要功能: oauth登录 发布文章(我称为"片段"),片段可以自定义一些和内容有关的指标,如“文中人物:12”.支持自定义排版.插图.建立相 ...
- Java——搭建自己的RESTful API服务器(SpringBoot、Groovy)
这又是一篇JavaWeb相关的博客,内容涉及: SpringBoot:微框架,提供快速构建服务的功能 SpringMVC:Struts的替代者 MyBatis:数据库操作库 Groovy:能与Java ...
- TypeScript 强类型 JavaScript – Rafy Web 框架选型
今天看到了 AngularJs 2.0 版本将基于 TypeScript 构建 的消息.与同事们对 TypeScript 展开了讨论.本文记录一些个人的想法. 理想的 JavaScript 开发模式 ...
- Swif - 可选型
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #4dbf56 } p.p2 { margin: 0.0px 0. ...
- 用Groovy构建java脚本
我是做工作流项目的,工作流中各个模板引擎都需要要执行一个动态业务,这些动态业务有多种实现方式,最常用的就是用户自己写一段脚本文件,然后工作流引擎执行到这里的时候,运行这个脚本文件. 这个运行脚本文件的 ...
- 移动端IM系统的协议选型:UDP还是TCP?
1.前言 对于有过网络编程经验的开发者来说,使用何种数据传输层协议来实现数据的通信,是个非常基础的问题,它涉及到你的第一行代码该如何编写. 从PC时代的IM开始,IM开发者就在为数据传输协议的选型争论 ...
随机推荐
- ASP.NET(C#)中的try catch异常处理机制
在开发一个Umbraco平台系统的过程中,遇到了问题. 写的代码如下 fileUrl = MediaHelper.GetMediaUrl(Convert.ToInt32(publishedConten ...
- [ASP.NET MVC] Child actions are not allowed to perform redirect
我在Umbraco平台下,用MVC(SurfaceController)开发时,遇到这个问题 MemberEdit是一个partial View [HttpGet] [ActionName(" ...
- HDU 1150 Machine Schedule (二分图最小点覆盖)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1150 有两个机器a和b,分别有n个模式和m个模式.下面有k个任务,每个任务需要a的一个模式或者b的一个 ...
- Light oj 1138 - Trailing Zeroes (III) (二分)
题目链接:http://lightoj.com/volume_showproblem.php?problem=1138 题目就是给你一个数表示N!结果后面的0的个数,然后让你求出最小的N. 我们可以知 ...
- HDU 5441 Travel (并查集+数学+计数)
题意:给你一个带权的无向图,然后q(q≤5000)次询问,问有多少对城市(城市对(u,v)与(v,u)算不同的城市对,而且u≠v)之间的边的长度不超过d(如果城市u到城市v途经城市w, 那么需要城市u ...
- ActiveMQ学习笔记(一) JMS概要
(一)什么是JMS jms即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送 ...
- C++中动态申请二维数组并释放方法
C/C++中动态开辟一维.二维数组是非常常用的,以前没记住,做题时怎么也想不起来,现在好好整理一下. C++中有三种方法来动态申请多维数组 (1)C中的malloc/free (2)C++中的new/ ...
- MSSQL索引优化
转自:http://blog.itpub.net/16436858/viewspace-589275/ http://www.cnblogs.com/jams742003/archive/2011/1 ...
- Oracle-11g-R2(11.2.0.3.x)RAC Oracle Grid & Database 零宕机方式升级 PSU(自动模式)
升级环境: 1.源库版本: Grid Infrastructure:11.2.0.3.13 Database:11.2.0.3.13 2.目标库版本: Grid Infrastructure:11.2 ...
- POJ2142——The Balance
刚学习的扩展欧几里得算法,刷个水题 求解 线性不定方程 和 模线性方程 求方程 ax+by=c 或 ax≡c (mod b) 的整数解 1.ax+by=gcd(a,b)的一个整数解: <sp ...