<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Groovy 實(shí)現(xiàn)代碼熱載的機(jī)制和原理

          共 5283字,需瀏覽 11分鐘

           ·

          2021-11-29 20:12

          你知道的越多,不知道的就越多,業(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è)條件:

          1. 該類所有的實(shí)例都已經(jīng)被GC

          2. 加載該類的ClassLoader已經(jīng)被GC

          3. 該類的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
          
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    2020天天干天天操 | 人妻A√无码一区三级无套 | 三级无码视频在线观看 | 无码人妻一区二区三区免费九色 | 一级性爱视频免费看 |