Groovy 實(shí)現(xiàn)代碼熱載的機(jī)制和原理
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進(jìn)!你不來,我和你的競爭對(duì)手一起精進(jìn)!
編輯:業(yè)余草
cnblogs.com/mumuxinfei/p/8387349.html
推薦:https://www.xttblog.com/?p=5292
我不太清楚有多少人使用過 Groovy 的熱更新功能,目前我們公司不少復(fù)雜的業(yè)務(wù)模塊,使用了它。最近還使用了 Groovy 的一個(gè)熱更新功能,感覺還算順手。本文總結(jié)一下 Groovy 實(shí)現(xiàn)代碼熱載的機(jī)制和原理!

前言
最近很忙,現(xiàn)在趁這段空閑的時(shí)間,對(duì)之前接觸的一些工程知識(shí)做下總結(jié)。先來講下借用 Groovy 如何來實(shí)現(xiàn)代碼的熱載,以及其中涉及到的原理和需要注意的點(diǎn)。
總的來說, Groovy作為一本動(dòng)態(tài)編譯語言, 其對(duì)標(biāo)應(yīng)該是c/c++體系中的lua, 在一些業(yè)務(wù)邏輯變動(dòng)頻繁的場景, 其意義非常的重大。
簡單入門
本文的主題是Groovy實(shí)現(xiàn)代碼熱載, 其他大背景是java實(shí)現(xiàn)主干代碼, groovy實(shí)現(xiàn)易變動(dòng)的邏輯代碼. 先來看下java是如何調(diào)用的groovy腳本的。
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
public class GroovyTest {
public static void main(String\[\] args) {
// \*) groovy 代碼
String script = "println 'hello'; 'name = ' + name;";
// \*) 傳入?yún)?shù)
Binding binding = new Binding();
binding.setVariable("name", "lilei");
// \*) 執(zhí)行腳本代碼
GroovyShell shell = new GroovyShell(binding);
Object res = shell.evaluate(script);
System.out.println(res);
}
}
這段代碼的輸出為:
Binding類主要用于傳遞參數(shù)集, 而GroovyShell則主要用于編譯執(zhí)行Groovy代碼。是不是比想象中的要簡答, ^_^。
當(dāng)然java調(diào)用groovy還有其他的方式, 下文會(huì)涉及到.
原理分析
下面這段其實(shí)大有文章.
GroovyShell shell = new GroovyShell(binding);
Object res = shell.evaluate(script);
對(duì)于函數(shù)evaluate, 我們追蹤進(jìn)去, 會(huì)有不少的重新認(rèn)識(shí).
public Object evaluate(GroovyCodeSource codeSource) throws CompilationFailedException {
Script script = this.parse(codeSource);
return script.run();
}
public Script parse(GroovyCodeSource codeSource) throws CompilationFailedException {
return InvokerHelper.createScript(this.parseClass(codeSource), this.context);
}
其大致的思路, 「為Groovy腳本代碼包裝生成class, 然后產(chǎn)生該類實(shí)例對(duì)象, 在具體執(zhí)行其包裝的邏輯代碼」.
但是這邊需要注意的情況:
public Class parseClass(String text) throws CompilationFailedException {
return this.parseClass(text, "script" + System.currentTimeMillis() + Math.abs(text.hashCode()) + ".groovy");
}
對(duì)于groovy腳本, 它默認(rèn)會(huì)生成名字為**script + System.currentTimeMillis() + Math.abs(text.hashCode())**的class類, 也就是說傳入腳本, 它都會(huì)生成一個(gè)新類, 「就算同一段groovy腳本代碼, 每調(diào)用一次, 都會(huì)生成一個(gè)新類」。
陷阱評(píng)估
原理我們基本上理解了, 但是讓我們來構(gòu)造一段代碼, 看看是否有哪些陷阱.
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import java.util.Map;
import java.util.TreeMap;
public class GroovyTest2 {
private static GroovyShell shell = new GroovyShell();
public static Object handle(String script, Map params) {
Binding binding = new Binding();
for ( Map.Entry ent : params.entrySet() ) {
binding.setVariable(ent.getKey(), ent.getValue());
}
Script sci = shell.parse(script);
sci.setBinding(binding);
return sci.run();
}
public static void main(String\[\] args) {
String script = "println 'hello'; 'name = ' + name;";
Map params = new TreeMap();
params.put("name", "lilei");
while(true) {
handle(script, params);
}
}
}
這段代碼執(zhí)行到最后的結(jié)果為, 頻繁觸發(fā)full gc, 究其原因?yàn)镻ermGen區(qū)爆滿. 這是為何呢?
如上所分析的, 雖然是同一份腳本代碼, 但是都為其每次調(diào)用, 間接生成了一個(gè)class類. 對(duì)于full gc, 除了清理老年代, 也會(huì)順便清理永久代(PermGen), 但為何不清理這些一次性的class呢? 答案是gc條件不成立。
引用下class被gc, 需滿足的三個(gè)條件:
該類所有的實(shí)例都已經(jīng)被GC
加載該類的ClassLoader已經(jīng)被GC
該類的java.lang.Class對(duì)象沒有在任何地方被引用
加載類的ClassLoader實(shí)例被GroovyShell所持有, 作為靜態(tài)變量(gc root), 條件2不成立, GroovyClassLoader有個(gè)map成員, 會(huì)緩存編譯的class, 因此條件3都不成立.
有人會(huì)問, 為何不把GroovyShell對(duì)象, 作為一個(gè)臨時(shí)變量呢?
public static Object handle(String script, Map params) {
Binding binding = new Binding();
for ( Map.Entry ent : params.entrySet() ) {
binding.setVariable(ent.getKey(), ent.getValue());
}
GroovyShell shell = new GroovyShell();
Script sci = shell.parse(script);
sci.setBinding(binding);
return sci.run();
}
實(shí)際上, 還是治標(biāo)不治本, 只是說class能被gc掉, 但是清理的速度可能趕不上產(chǎn)生的速度, 依舊頻繁觸發(fā)full gc。
推薦做法
解決上述問題很簡單, 就是「引入緩存」, 當(dāng)然緩存的對(duì)象不上Script實(shí)例(在多線程環(huán)境下, 會(huì)遇到數(shù)據(jù)混亂的問題, 對(duì)象有狀態(tài)), 而是「Script.class本身」. 對(duì)應(yīng)的key為腳本代碼的指紋.
大致的代碼如下所示:
private static ConcurrentHashMap
2020天天干天天操
|
人妻A√无码一区三级无套
|
三级无码视频在线观看
|
无码人妻一区二区三区免费九色
|
一级性爱视频免费看
|
