哪个大神能提取一个魅族天气最新版

让天下没有难学的技术
《Groovy官方文档》1.3 Groovy和Java比较
《Groovy官方文档》1.3 Groovy和Java比较
译者:jackWang
Groovy语言一直在努力亲近Java开发人员。在设计Groovy语言的时候,我们遵循最小标新立异原则,努力让那些Java开发背景的开发者容易上手并学会。下面我们列举Groovy和Java的一些主要区别。
1 默认导入
下面的包和类是默认导入的,也就是说不必精确使用 import 语句来导入它们:
java.lang.*
java.math.BigDecimal
java.math.BigInteger
java.net.*
java.util.*
groovy.lang.*
groovy.util.*
2 动态方法(Multi-methods)
在Groovy里,方法的调用是在运行时动态决定。这一特性叫做运行时分发(runtime dispatch)或动态方法(multi-methods)。也就是说方法的最后调用是根据传入参数在运行时的类型所决定。在Java里,这一点是不一样的:在编译时就决定了方法的参数类型。
下面的代码,我们采用Java风格,在Groovy和Java都可以编译通过,但是运行结果不一样:
int method(String arg) {
int method(Object arg) {
Object o = "Object";
int result = method(o);
在Java里,结果是
assertEquals(2, result);
但是Groovy里,结果是
assertEquals(1, result);
原因是Java使用的是静态声明的类型信息,这里o被声明为Object,但是Groovy是在运行时决定,当方法最终被调用时,因为这里o实际是一个字符串,因此最终String版本的方法被调用。
译者注:译者之前也没有接触过Groovy语言,空闲时间也是有限的(姑且让我找这个借口吧,虽然这个借口很牵强,对待知识本来应该以一种严谨,求真的态度)因此这里有些专有名字可能翻译不是很准确,比如对Multi-methods的翻译。译者也不确定是否准确,因此附带了原文单词,请读者自行根据示例代码和上下文意思理解。如果找到准确的中文翻译恳请评论留言,以待修正。
3 数组初始化
在Groovy,{…}已经被用作闭包,也就是说你不能使用下面的语法创建数组(译者注:Java可以,并且很常用)
int[] array = { 1, 2, 3}
你应该这样声明并初始化一个数组
int[] array = [1,2,3]
4 包范围可见性(Package scope visibility)
在Groovy里,省略字段的修饰符不会像Java一样使其成为包私有属性(package-private field)
class Person {
String name
这里,我们创建了一个属性,它是私有的,而且自动关联了getter和setter方法。如果我们要创建一个包私有属性,可以添加@PackageScope注解来实现:
class Person {
@PackageScope String name
ARM(Automatic Resource Management 自动资源管理)块从Java7开始支持,但是Groovy不支持。相应地,Groovy依赖于闭包来实现类似的功能。示例:
Path file = Paths.get("/path/to/file");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
在Groovy里可以写成:
new File('/path/to/file').eachLine('UTF-8') {
println it
或者也可以写成跟Java类似风格:
new File('/path/to/file').withReader('UTF-8') { reader -&
reader.eachLine {
println it
Groovy遵循了Java的匿名内部类以及嵌套内的特点。但是它并没有完全依照Java语言规范,因此在使用前应该记住它们是有区别的。Groovy的实现和groovy.lang.Clouser类的风格有些类似,但也有不同点。比如在访问私有字段和方法以及局部变量没有final等。
6.1 静态内部类
这是一个静态内部类的例子:
static class B {}
使用静态内部类是一个非常好的实践,如果你一定要使用内部类,建议优先考虑静态内部类。
6.2 匿名内部类
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
CountDownLatch called = new CountDownLatch(1)
Timer timer = new Timer()
timer.schedule(new TimerTask() {
void run() {
called.countDown()
assert called.await(10, TimeUnit.SECONDS)
6.3 创建非静态内部类实例
在Java里,你可以这样写:
public class Y {
public class X {}
public X foo() {
return new X();
public static X createX(Y y) {
return y.new X();
Groovy不支持y.new X()语法,但你可以写成new X(y),像下面的代码:
public class Y {
public class X {}
public X foo() {
return new X()
public static X createX(Y y) {
return new X(y)
特别注意,Groovy支持调用无参方法传入一个参数。那个参数的值将会是null。这个特性对于调用构造函数同样适用。可能会有人写new X(this)而不是new X(),这是不合法的。虽然我们还没有找到办法避免用户这样写。
拉姆达表达式
Java 8 支持拉姆达表达式和方法引用
Runnable run = () -& System.out.println("Run");
list.forEach(System.out::println);
Java8的拉姆达表达式或多或少被认为是匿名内部类。Groovy不支持这样的语法,但是可以使用闭包代替:
Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)
使用双引号修饰的字符串被解释为GString值。如果一个字符串里含有美元符号在Groovy和Java的编译器里将会产生编译错误。
当然,Groovy会自动在GString和String之间进行类型转换,就像Java可以接受一个Object参数然后检查其实际类型一样。
字符串和字符
在Groovy里,使用单引号修饰的被当成String类型,使用双引号修饰的可以当成GString类型或String类型。取决于字面常量。
assert 'c'.getClass()==String
assert "c".getClass()==String
assert "c${1}".getClass() in GString
如果声明是char类型,Groovy会自动将单个字符从String类型转换为char类型。如果被调用的方法声明的参数类型是char,我们需要强制类型转换为char类型。
char a='a'
assert Character.digit(a, 16)==10 : 'But Groovy does boxing'
assert Character.digit((char) 'a', 16)==10
assert Character.digit('a', 16)==10
assert false: 'Need explicit cast'
} catch(MissingMethodException e) {
Groovy支持两种风格的类型转换,在转换成char类型的时候,当个字符和多个字符转换有些不一样。对于多个字符转换成char类型,Groovy会选择第一个字符,这一点不像C语言,会直接失败。
// for single char strings, both are the same
assert ((char) "c").class==Character
assert ("c" as char).class==Character
// for multi char strings they are not
((char) 'cx') == 'c'
assert false: 'will fail - not castable'
} catch(GroovyCastException e) {
assert ('cx' as char) == 'c'
assert 'cx'.asType(char) == 'c'
10 ==的行为
在Java里,==意味着基本类型相等或对象类型相等。在Groovy里,==会转换成a.compareTo(b)==0,如果他们是Comparable,就是使用a.equals(b),否则检查基本类型,也就是is,比如a.is(b)
不同的关键字
Groovy比Java有更多的关键字,请不要把它们当变量名使用
原创文章,转载请注明: 转载自本文链接地址:
攻城狮一枚三好青年——好吃,好玩,好技术
Latest posts by JackWang ()
共享此文章:
Related posts:
(1 votes, average: 5.00 out of 5)
Loading...966,690 五月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
从Groovy到Java 8
从Groovy到Java 8
日. 估计阅读时间:
道AI风控、Serverless架构、EB级存储引擎,尽在!
相关厂商内容
相关赞助商
全球技术领导力峰会日-7月1日,上海&宝华万豪酒店,
def closure = {
assert closure instanceofjava.util.concurrent.Callable
assert closure() == &called&
通过转换closure的类型,我们可以让Groovy实现其他函数式接口。
public interface Function {
def apply();
def closure = {
} as Function
assert closure instanceof Function
assert closure.apply() == &applied&
在Java 8中很好地引入了闭包和函数式编程的思想。在Java即将发布的版本中函数式接口极为重要,因为在Java 8中针对新引入的Lambda函数式接口提供了隐含的实现。
我们可以把Lambda函数当成Groovy中的闭包那样去理解和使用。在Java 8中实现callable接口像Groovy中的闭包一样简单。
Callable callable = () -& &called&;
assert callable.call() == &called&;
你需要特别注意是,Java 8为单行的lambda函数提供了隐含的返回语句,后来Groovy也借鉴了这个概念。将来,Groovy也会为单个抽象方法提供隐含实现(类似于Java 8提供的那些实现)。这个特性使你不必完全派生出closures的具体子类对象就可以使用实例的属性和方法。
abstract class WebFlowScope {
private static final Map scopeMap = [:]
abstractdefgetAttribute(def name);
publicdef put(key, val) {
scopeMap[key] = val
getAttribute(key)
protected Map getScope() {
WebFlowScope closure = { name -&
&edited_${scope[name]}&
assert closure instanceofWebFlowScope
assert closure.put(&attribute&, &val&) == &edited_val&
Java 8针对带有接口默认方法的函数式接口提出了一个类似的概念,即Java的新概念&接口默认方法&。他们希望借此概念在不违反接口实现规约(在Java之前的版本中建立的实现规约)的前提下改进核心的API。
当把Lambda函数强制转型为接口时,它们也可以使用接口的默认方法。也就是说在接口中可以内置健壮的API,使开发人员不必改变类型的种类或规约就可以使用这些API。
public interface WebFlowScope {
static final Map scopeMap = new HashMap();
Object getAttribute(Object key);
default public Object put(Object key, Object val) {
scopeMap.put(key, val);
return getAttribute(key);
default Map getScope() {
return scopeM
static final WebFlowScope scope = (Object key) -&
&edited_& + scope.getScope().get(key);
assert scope.put(&attribute&, &val&) == &val&;
Java 8中的接口默认方法还可以帮我们实现像memoization和trampolining这样的Groovy特性。你可以很简单就实现memoization特性,只需要创建一个带有接口默认方法的函数式接口,并实现这个默认方法让它从缓存中确定估算结果或返回结果就可以了。
public interface MemoizedFunction&T, R& {
static final Map cache = new HashMap();
R calc(T t);
public default R apply(T t) {
if (!cache.containsKey(t)) {
cache.put(t, calc(t));
return (R)cache.get(t);
static final MemoizedFunction&Integer, Integer& fib
= (Integer n) -& {
if (n == 0 || n == 1)
return fib.apply(n - 1)+fib.apply(n-2);
assert fib.apply(20) == 6765;
同样,我们还可以使用Java 8的接口默认方法开发Trampoline的实现。Trampoline是Groovy的一种递归策略,这个特性非常适用于深度递归,而不可能取代Java的调用栈。
interfaceTrampolineFunction&T, R& {
R apply(T...obj);
public default Object trampoline(T...objs) {
Object result = apply(objs);
if (!(result instanceofTrampolineFunction)) {
// Wrap the call in a TrampolineFunction so that
we can avoid StackOverflowError
static TrampolineFunction&Integer, Object&
fibTrampoline = (Integer...objs) -& {
Integer n = objs[0];
Integer a = objs.length&= 2 ? objs[1] : 0;
Integer b = objs.length&= 3 ? objs[2] : 1;
if (n == 0)
else return fibTrampoline.trampoline(n-1, b, a+b);
除了closures的基本特性以及那些Memoization和Trampolining的高级特性,Groovy还为Collections API提供了一些有巨大实用价值的语言扩展。我们在使用Groovy时可以充分利用这些扩展点,比如用list 的&each&方法非常简捷地完成写操作。
def list = [1, 2, 3, 4]
list.each { item -&
println item
Java 8针对集合的迭代引入了一种与Groovy类似的概念,提供了一个与&each&相似的&forEach&方法,可以用它取代list传统的迭代方式。
List&Integer& list = new ArrayList&&();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.forEach( (Integer item) -&System.out.println(item); );
除了简化list的迭代,Groovy还为应用开发人员提供了各种快捷写法以简化各类list操作。比如&collect&方法,你用这个方法可以将list元素快速映射为新的类型(或新的值),然后把结果放入新的list里。
def list = [1, 2, 3, 4]
defnewList = list.collect { n -& n * 5 }
assert newList == [5, 10, 15, 20]
在Groovy中&collect&的实现比较简单,你只需要把映射当作一个参数传递给&collect&方法。但是,Java 8的实现就稍微有点复杂了,开发人员可以使用Java 8的StreamAPI实现同样的映射和收集策略,实现时要调用&list&的&stream&组件的&map&方法,然后再调用&map&方法返回的&stream&的&collect&方法。开发人员可以这样连续使用Stream API完成list一连串的操作。
List&Integer& list = new ArrayList&&();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
List&Integer&newList = list.stream().map((Integer n) -& n * 5).collect(Collectors.toList());
assert newList.get(0) == 5 &&newList.get(1) == 10
&&newList.get(2) == 15 &&newList.get(3) == 20;
Groovy还能让开发人员使用&findAll&方法简捷地筛选list。
def emails = ['', '',
'daniel.', '']
defgmails = emails.findAll { it.endsWith('@') }
assert gmails = ['', '']
同样地,Java 8开发人员可以使用Stream API筛选list。
List&String& emails = new ArrayList&&();
emails.add(&&);
emails.add(&&);
emails.add(&daniel.&);
emails.add(&&);
List&String&gmails = emails.stream().filter(
(String email) -&email.endsWith(&@&) ).collect(Collectors.toList());
assert gmails.get(0) == &&
&&gmails.get(1) == &&;
Groovy Collections API扩展还提供了一个&sort&方法,你使用这个方法可以简单地完成对list的排序。&sort&方法还可以接受闭包参数,你可以在闭包中实现所需的特定排序逻辑,闭包会被转为比较器后完成对list的排序。另外,如果只需要对list进行简单地逆序排序,可以调用&reverse&方法反转list的顺序。
def list = [2, 3, 4, 1]
assert list.sort() == [1, 2, 3, 4]
assert list.sort { a, b -& a-b &=& b } == [1, 4, 3, 2]
assert list.reverse() == [2, 3, 4, 1]
再来看Java 8的Stream API,我们可以使用&sorted&方法对list排序,然后用&toList&方法收集排序结果。&sorted&方法也支持自定义的比较器,它有一个可选的函数式参数(比如Lambda函数),你可以将自定义的比较器作为参数传给方法,就可以很容易地实现特定的排序逻辑和反转list条目的操作了。
List&Integer& list = new ArrayList&&();
list.add(2);
list.add(3);
list.add(4);
list.add(1);
list = list.stream().sorted().collect(Collectors.toList());
assert list.get(0) == 1 &&list.get(3) == 4;
list = list.stream().sorted((Integer a, Integer b) &br/&-&Integer.valueOf(a-
b).compareTo(b)).collect(Collectors.toList());
assert list.get(0) == 1 &&list.get(1) == 4 &&list.&br/&get(2) == 3 &&list.get(3) == 2;
list = list.stream().sorted((Integer a, Integer b) &br/&-&pareTo(a)).collect(Collectors.toList());
assert list.get(0) == 2 &&list.get(3) == 1;
如果你试图在一个闭包或Lambda函数内完成所有的处理而连续调用API(比如list streaming),那么很快就会使代码难以维护。换一个角度来看,如果你要委托相应工作单元特定的方法完成特定的处理,那么这种用法就是一个不错的选择了。
我们使用Groovy时,把方法引用传给函数也可以实现上面所说的目标。你只要使用&.&&操作符去引用方法,就可以把该方法强制转型为闭包传给另一个方法了。由于可以从外部源码引入过程代码,就从本质上提高了实现的灵活性。这样,开发人员就可以在逻辑上组织处理方法,完成更易维护、可持续演进的应用架构了。
def modifier(String item) {
&edited_${item}&
def list = ['item1', 'item2', 'item3']
assert list.collect(this.&modifier) == ['edited_item1'
, 'edited_item2', 'edited_item3']
Java 8也为开发人员提供了同样的灵活性,使开发人员可以使用&::&操作符获得方法的引用。
List&String& strings = new ArrayList&&();
strings.add(&item1&);
strings.add(&item2&);
strings.add(&item3&);
strings = strings.stream().map(Helpers::modifier).
collect(Collectors.toList());
assert &edited_item1&.equals(strings.get(0));
assert &edited_item2&.equals(strings.get(1));
assert &edited_item3&.equals(strings.get(2));
你可以把方法引用传给任意以函数式接口为形参的方法。那么,这个方法就会被转型为函数式接口,作为函数式接口执行。
public interface MyFunctionalInterface {
boolean apply();
void caller(MyFunctionalInterfacefunctionalInterface) {
assert functionalInterface.apply();
booleanmyTrueMethod() {
caller(Streaming::myTrueMethod);
在Java 8里,如果类库开发人员修改了接口规约,那么这些接口的使用者不必为了这些变更去修改那些使用了这个类库的接口。
这些概念和编程风格的无缝转化是从Groovy到Java 8的一次具有重要意义的过渡。Groovy为了提高内部灵活性和改进Java原有的API,使用了大量的JVM空间。随着这些改进在Java 8里生根发芽,意味着两种语言将有更多的相同点、而不同点会越来越少,事实上这正是本文要介绍的主要内容。当学习和使用这些新API、新特性和新概念时(从Java 8引入到Java生态系统中),熟练的Groovy开发人员只需要更短的学习曲线。
Daniel Woods是Object Partners有限公司的一名高级顾问。他专门从事于Groovy和Grails应用架构的研究,对Java和其他基于JVM的语言一直抱有浓厚的兴趣。它是一名开源贡献者,并出席了Gr8Conf和SpringOne 2GX的本年度年会。可以通过电子邮件()或Twitter (@danveloper)与Daniel取得联系。
查看英文原文:
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博()或者腾讯微博()关注我们,并与我们的编辑和其他读者朋友交流。
Author Contacted
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
代码粘在一起了..
用groovy感觉确实比java 快多了
用groovy感觉确实比java 快多了
用groovy感觉确实比java 快多了
用groovy感觉确实比java 快多了
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
找回密码....
InfoQ账号使用的E-mail
关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。
内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。
设置通知机制以获取内容更新对您而言是否重要
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。Java动态调用Groovy | Neeao> 在Java里整合Groovy脚本的一个陷阱
在Java里整合Groovy脚本的一个陷阱
最近在项目里要在Java中整合Groovy脚本来粘合各个组件/服务,所以这两天在测试几种整合方法。最初是想用JSR 223系的API,不过我们这边对ClassLoader有特别需求,JSR 223的API满足不了,所以还是转而考虑Groovy自身的整合机制。 除了与之外,整合Groovy基本上有三种途径:(以及)、和。这些在官网的文档上有所描述,在几本Groovy的书里也有提及。 然而在整合Groovy脚本的时候可能会遇到一类陷阱:临时加载的类未能及时被释放,进而导致PermGen OutOfMemoryError;没那么严重的时候也会引发比较频繁的full GC从而影响稳定运行时的性能。 如果只是要执行一些Groovy脚本,那么GroovyShell看来是个不错的选择。于是用它做个小测试: (环境在后面的截图里有写,这里就不详细说了。Windows XP SP3/Sun JDK 1.6.0u18/client默认参数/Groovy 1.7.1)
package fx.
import groovy.lang.GroovyS
import groovy.lang.S
import java.io.IOE
* @author sajia
public class TestGroovyShell {
// see if the number of loaded class keeps growing when
// using GroovyShell.parse
public static void test() {
GroovyShell shell = new GroovyShell();
String scriptText = &def mul(x, y) { x * y }\nprintln mul(5, 7)&;
while (true) {
Script script = shell.parse(scriptText);
Object result = script.run();
public static void main(String[] args) {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
启动这个程序,按一下回车,放着跑不到一分钟就会看到异常:
Exception in thread &main& java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:55)
at groovy.lang.GroovyClassLoader$ClassCollector.createClass(GroovyClassLoader.java:496)
at groovy.lang.GroovyClassLoader$ClassCollector.onClassNode(GroovyClassLoader.java:513)
at groovy.lang.GroovyClassLoader$ClassCollector.call(GroovyClassLoader.java:517)
at org.codehaus.pilationUnit$11.call(CompilationUnit.java:767)
at org.codehaus.pilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:971)
at org.codehaus.pilationUnit.doPhaseOperation(CompilationUnit.java:519)
at org.codehaus.pilationUnit.processPhaseOperations(CompilationUnit.java:497)
at org.codehaus.pile(CompilationUnit.java:474)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:292)
at groovy.lang.GroovyShell.parseClass(GroovyShell.java:727)
at groovy.lang.GroovyShell.parse(GroovyShell.java:739)
at groovy.lang.GroovyShell.parse(GroovyShell.java:766)
at groovy.lang.GroovyShell.parse(GroovyShell.java:757)
at fx.test.TestGroovyShell.test(TestGroovyShell.java:20)
at fx.test.TestGroovyShell.main(TestGroovyShell.java:31)
如果在启动这个测试时加上-verbose选项,可以看到每次执行GroovyShell.parse()方法时都会打印出这样的日志:
[Loaded Script183 from file:/groovy/shell]
[Loaded Script183$mul from file:/groovy/shell]
也就是说上面测试中的脚本每次被parse()都新生成两个类,一个对应顶层代码,一个对应其中的mul()方法。在循环中调用parse()方法,不消一会儿就把HotSpot的PermGen给撑爆了;虽然执行过程中也可以看到PermGen的空间紧张经常引发full GC,而在full GC时会卸载掉许多不再有引用的类,但这个测试中卸载的速度没有生成的速度快,就杯具了。 除了类自身之外,类中的常量池所引用的字符串也都需要被intern,上面的例子中像&mul&这个名字就会被intern掉;在HotSpot 中,intern的String实例也是在PermGen上分配空间的。内容相同的字符串就算被intern很多次在PermGen的字符串池里也只会有一份,不过如果连续执行很多脚本,脚本里在&成员&和&类型&级别上出现了很多不同的标识符的话,这也会对字符串池造成压力。 用JConsole可以形象的看到PermGen爆掉的过程。下面两张截图中右边骤然下降的线是在测试程序抛出异常而终止后JConsole与之连接被断开的时候的,可以忽略掉。
(补一张PermGen趋势截图)
========================================================================== Sun JDK 1.6.0u18的HotSpot在32位Windows XP SP3上默认选用client模式,默认PermGen大小是64MB。如果在上面的测试里给入参数-XX:MaxPermSize=512m,将 PermGen最大大小设置到512MB,情况会怎样呢?放着让它多跑几分钟,会看到:
Exception in thread &main& java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Unknown Source)
at java.lang.String.&init&(Unknown Source)
at java.lang.StringBuffer.toString(Unknown Source)
at java.net.URLStreamHandler.toExternalForm(Unknown Source)
at java.net.URL.toExternalForm(Unknown Source)
at java.net.URL.toString(Unknown Source)
at java.lang.ClassLoader.defineClassSourceLocation(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at org.codehaus.groovy.reflection.ClassLoaderForClassArtifacts.define(ClassLoaderForClassArtifacts.java:27)
at org.codehaus.groovy.reflection.ClassLoaderForClassArtifacts$1.run(ClassLoaderForClassArtifacts.java:71)
at org.codehaus.groovy.reflection.ClassLoaderForClassArtifacts$1.run(ClassLoaderForClassArtifacts.java:69)
at java.security.AccessController.doPrivileged(Native Method)
at org.codehaus.groovy.reflection.ClassLoaderForClassArtifacts.defineClassAndGetConstructor(ClassLoaderForClassArtifacts.java:69)
at org.codehaus.groovy.runtime.pilePojoMethod(CallSiteGenerator.java:227)
at org.codehaus.groovy.reflection.CachedMethod.createPojoMetaMethodSite(CachedMethod.java:244)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.createCachedMethodSite(PojoMetaMethodSite.java:158)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.createPojoMetaMethodSite(PojoMetaMethodSite.java:147)
at groovy.lang.MetaClassImpl.createPojoCallSite(MetaClassImpl.java:2994)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createPojoSite(CallSiteArray.java:114)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:148)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:40)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
at org.codehaus.groovy.ast.builder.AstBuilderInvocationTrap.visitMethodCallExpression(AstBuilderTransformation.groovy:179)
at org.codehaus.groovy.ast.expr.MethodCallExpression.visit(MethodCallExpression.java:67)
at org.codehaus.groovy.ast.CodeVisitorSupport.visitExpressionStatement(CodeVisitorSupport.java:69)
at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:40)
at org.codehaus.groovy.ast.CodeVisitorSupport.visitBlockStatement(CodeVisitorSupport.java:35)
at org.codehaus.groovy.ast.stmt.BlockStatement.visit(BlockStatement.java:51)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
如果看JConsole监视到的类加载状况,会看到:
右边陡然下降的曲线跟上一个测试一样是在抛了异常之后的部分,可以忽略。 中间一段看起来很平,看来是没问题? 其实不然。如果结合程序的执行速度与GC消耗的时间来看,会发现加载类的数量的曲线比较平的这段时间里,上面测试代码的每轮循环都要等很久才会输出一个35,而大部分时间都消耗在了full GC上;这是由于&某种原因&(*)使得GC堆的年老代非常满,于是稍微分配一点空间就要触发full GC。最终GC堆还是没撑住,就爆了。 也就是说这次没有让PermGen爆掉只不过是因为瓶颈转移到别的部分了而已。 *:这个&某种原因&以后或许会发篇帖分析一下。这篇就只谈谈现象吧。 ========================================================================== GroovyShell上的几个方法都有同样的问题,像是evaluate()的各个重载、parse(),还有Eval.me()/x/xy()/xyz()这些方法都一样。 当然,在上面的测试中只要把shell.parse(scriptText);这句移到循环的外面就可以避免撑爆PermGen的问题&&因为只调用了一次parse()方法,相应的也就只生成了对应的那些新的类。 于是这里就有个启示:如果嵌入GroovyShell的场景需要经常执行Groovy脚本,那么或许应该通过weak cache来检查先前是不是已经处理过当前输入的脚本,没处理过的时候才去调用GroovyShell.parse()并将脚本记录到weak cache里。 ========================================================================== 如果GroovyShell可能导致PermGen问题,那GroovyClassLoader是不是也一样会呢?换用下面的代码来测试的话:
package fx.
import groovy.lang.GroovyClassL
import java.io.IOE
* @author sajia
public class TestGroovyClassLoader {
// see if the number of loaded class keeps growing when
// using GroovyClassLoader.parseClass
public static void test() {
GroovyClassLoader loader = new GroovyClassLoader();
String scriptText = &class Foo {\n&
int add(int x, int y) { x + y }\n&
Class&?& clazz =
while (true) {
Class&?& newClazz = loader.parseClass(scriptText);
if (clazz == newClazz) {
System.out.println(&class cached&);
clazz = newC
public static void main(String[] args) {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
却发现它跑起来没导致PermGen OOM。 同样看看JConsole监控的截图:
可以看到虽然被加载的类仍然非常多,但多数都及时被卸载了所以PermGen能动态维持在一个不太满的水平上。 观察-verbose得到的日志,可以看到上面例子中每次调用GroovyClassLoader.parseClass()只生成并加载了一个类:
[Loaded Foo from file:/groovy/script]
虽然每次生成并加载的类的数量比GroovyShell.parse()的少,但这个测试总觉得缺了点什么。对,没对那些新生成的类生成过实例。那么改一下,加上对Class.newInstance()的调用:
Class&?& clazz =
while (true) {
Class&?& newClazz = loader.parseClass(scriptText);
newClazz.newInstance(); // make new instance!
} catch (Exception e) {
e.printStackTrace();
if (clazz == newClazz) {
System.out.println(&class cached&);
clazz = newC
则类加载与PermGen的表现又有所不同了:
虽然还是没有因为PermGen而OOM,但PermGen的压力明显比不调用newInstance()时高了些。 接下来,模仿我们这边已有的一个项目里对Groovy的用法,加上对新生成的实例的方法调用再来测试一下:
public static void test() {
String scriptText = &class Foo {\n&
int add(int x, int y) { x + y }\n&
Class&?& clazz =
while (true) {
GroovyClassLoader loader = new GroovyClassLoader();
Class&?& newClazz = loader.parseClass(scriptText);
Object obj = newClazz.newInstance();
Object i = obj.getClass()
.getMethod(&add&, int.class, int.class)
.invoke(obj, 2, 3);
} catch (Exception e) {
e.printStackTrace();
if (clazz == newClazz) {
System.out.println(&class cached&);
clazz = newC
结果也还正常,跑了十几分钟都没有OOM,也没有表现出OOM的倾向。Good。 ========================================================================== 说来GroovyShell里还特别写了注释说不缓存脚本:
private Class parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException {
// Don't cache scripts
return loader.parseClass(codeSource, false);
不乱缓存东西或许也算是一种美德吧&&? GroovyShell.parse()内部其实也就是调用GroovyClassLoader.parseClass()去解析Groovy脚本并生成Class实例(会是groovy.lang.Script的子类),然后调用Class.newInstance()构造出一个新的实例以 Script类型的引用返回出来。 既然它默认不缓存东西,怎么上面的例子里用它就会PermGen OOM而直接用GroovyClassLoader就没事呢?看来是两个例子中脚本的内容不同带来了差异。不过换成下面的版本来测却并没出问题:
package fx.
import groovy.lang.GroovyClassL
import groovy.lang.S
import java.io.IOE
* @author sajia
public class TestGroovyClassLoader {
// see if the number of loaded class keeps growing when
// using GroovyClassLoader.parseClass
public static void test() {
String scriptText = &def mul(x, y) { x * y }\nprintln mul(5, 7)&;
while (true) {
GroovyClassLoader loader = new GroovyClassLoader();
Class&?& newClazz = loader.parseClass(scriptText);
Object obj = newClazz.newInstance();
Script script = (Script)
script.run();
} catch (Exception e) {
e.printStackTrace();
public static void main(String[] args) {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
用GroovyShell的时候什么地方挂住了什么不该挂住的引用么&&? 下次再找原因吧&&
本文地址 :
------分隔线----------------------------

我要回帖

 

随机推荐