Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

运行长时间后内存泄露 #392

Closed
hillfly opened this issue Jun 15, 2021 · 14 comments
Closed

运行长时间后内存泄露 #392

hillfly opened this issue Jun 15, 2021 · 14 comments

Comments

@hillfly
Copy link

hillfly commented Jun 15, 2021

使用版本:5.2.5
涉及脚本内容举例
都是简单的逻辑判断,然后返回简单的数值

let isMatched = ($a > $b && $......)
if (isMatched  == true) {
     return something....
}

编译:
Expression expression = AviatorEvaluator.getInstance().compile(scriptText, false);
localCache.put(id, expression );
....

决策:

Map<String, Object> params = new HashMap();
// put params....

Expression expression = localCache.get(id);
Object ret = expression.execute(params);

使用方式:
系统启动时会自动加载编写好的脚本,然后编译并缓存Expression(自己实现的一套本地缓存)。
系统使用Dubbo对外提供服务,请求会把所需的入参变量传入,从本地缓存取得Expression后决策返回结果。

问题描述:
内存占用越来越大,GC也无效。
进服务器dump了后发现内存几乎被Thread吃满,然后发现是因为ThreadLocalMap中的元素无法被回收
进一步分析跟到了LambdaFunction中的Env,里面的内容看起来是每一次请求的入参信息,并没有随着请求结束而回收。

若把脚本全部改为类似下面这种不涉及高级语法的,就不会出现以上问题。

$a > $b && $a > 0
@killme2008
Copy link
Owner

killme2008 commented Jun 15, 2021

请给出最小可复现的 demo,LambdaFunction 里的 Env 只是闭包的 closure context,并不会往里面添加东西。

@killme2008
Copy link
Owner

我写了个最简单的例子了:

package com.googlecode.aviator.example;

import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;


public class SimpleExample {
  public static void main(final String[] args) throws Exception {
    Expression exp =
        AviatorEvaluator.compile("let x = a > 1 && b>0 ; if(x) { return true; } else { false} ");

    int i = 0;
    while (true) {
      exp.execute(exp.newEnv("a", i, "b", i));
      i = i + 1;
    }
  }
}

无限循环执行, jstat 观察 gc:

 S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  12.50   0.00   0.51  88.60  81.04    107    0.054     0    0.000    0.054
 12.50   0.00  52.00   0.51  88.60  81.04    152    0.073     0    0.000    0.073
 12.50   0.00   0.00   0.51  88.60  81.04    190    0.090     0    0.000    0.090
  0.00  12.50  32.00   0.51  88.60  81.04    229    0.108     0    0.000    0.108
  6.25   0.00   8.00   0.51  88.60  81.04    270    0.125     0    0.000    0.125
  0.00  12.50   0.00   0.51  88.60  81.04    305    0.141     0    0.000    0.141
 12.50   0.00  74.00   0.51  88.60  81.04    346    0.159     0    0.000    0.159
 12.50   0.00   2.00   0.51  88.60  81.04    388    0.177     0    0.000    0.177
  6.25   0.00  16.00   0.51  88.60  81.04    430    0.195     0    0.000    0.195
  6.25   0.00   0.00   0.51  88.60  81.04    468    0.212     0    0.000    0.212
  0.00   6.25   0.00   0.51  88.60  81.04    511    0.230     0    0.000    0.230
  0.00   6.25   2.00   0.51  88.60  81.04    553    0.248     0    0.000    0.248
  0.00  12.50  14.00   0.51  88.60  81.04    593    0.265     0    0.000    0.265
 12.50   0.00   0.00   0.51  88.60  81.04    636    0.283     0    0.000    0.283
  0.00  12.50  86.00   0.51  88.60  81.04    679    0.302     0    0.000    0.302
  6.25   0.00  80.00   0.51  88.60  81.04    720    0.320     0    0.000    0.320
 12.50   0.00   0.00   0.51  88.60  81.04    758    0.337     0    0.000    0.337
  6.25   0.00  16.00   0.51  88.60  81.04    794    0.354     0    0.000    0.354
  0.00  12.50   6.00   0.51  88.60  81.04    835    0.371     0    0.000    0.371
  0.00   6.25   0.00   0.51  88.60  81.04    877    0.389     0    0.000    0.389
  0.00  18.75  98.00   0.51  88.60  81.04    921    0.406     0    0.000    0.406
  0.00  12.50   6.00   0.51  88.60  81.04    969    0.426     0    0.000    0.426
  6.25   0.00  54.00   0.51  88.60  81.04   1016    0.444     0    0.000    0.444
 12.50   0.00   0.00   0.51  88.60  81.04   1058    0.462     0    0.000    0.462
  0.00   6.25   0.00   0.51  88.60  81.04   1101    0.480     0    0.000    0.480
  0.00  18.75  64.00   0.51  88.60  81.04   1141    0.497     0    0.000    0.497
  6.25   0.00   0.00   0.51  88.60  81.04   1184    0.514     0    0.000    0.514
  0.00   6.25   0.00   0.51  88.60  81.04   1229    0.533     0    0.000    0.533
  0.00   6.25   0.00   0.51  88.60  81.04   1277    0.552     0    0.000    0.552
  0.00   6.25  16.00   0.51  88.60  81.04   1323    0.570     0    0.000    0.570
 12.50   0.00   0.00   0.51  88.60  81.04   1368    0.588     0    0.000    0.588
  6.25   0.00  86.00   0.51  88.60  81.04   1418    0.608     0    0.000    0.608

可以看到内存基本没变化。

倾向于是你用法上或者缓存的实现问题。

@hillfly
Copy link
Author

hillfly commented Jun 15, 2021

抱歉忙着忘了回复。

import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test1 {
    private static Expression expression = compile();
    private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 200; ++i) {
            poolExecutor.execute(() -> {
                Map<String, Object> params = new HashMap<>();
                params.put("$field2", new Random().nextInt());

                expression.execute(params);
            });

            Thread.sleep(5);
        }
    }

    private static Expression compile() {
        String script = "fn func13958cf27099476b3c208d422dbfef3c() {\n" +
                "    let $field1 = nil;\n" +
                "\n" +
                "    let isMatched = ($field2 >= 3);\n" +
                "    if(isMatched == true){\n" +
                "        $field1 = 'D940';\n" +
                "    }\n" +
                "\n" +
                "    return seq.map('$field1', $field1, '$field2', $field2);\n" +
                "}\n" +
                "\n" +
                "func13958cf27099476b3c208d422dbfef3c()";

        return AviatorEvaluator.getInstance().compile(script, false);
    }
}

涉及脚本

fn func13958cf27099476b3c208d422dbfef3c() {
    let $field1 = nil;

    let isMatched = ($field2 >= 3);
    if(isMatched == true){
        $field1 = 'D940';
    }

    return seq.map('$field1', $field1, '$field2', $field2);
}

func13958cf27099476b3c208d422dbfef3c()

实际使用是Dubbo调用,所以这里用线程池去模拟Dubbo的线程池。我本地dubug断点发现最后thread pool中所有Thread所持有的threadlocalMap都没有释放,里面都是Env即请求的入参信息。

1 similar comment
@hillfly
Copy link
Author

hillfly commented Jun 15, 2021

抱歉忙着忘了回复。

import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test1 {
    private static Expression expression = compile();
    private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 200; ++i) {
            poolExecutor.execute(() -> {
                Map<String, Object> params = new HashMap<>();
                params.put("$field2", new Random().nextInt());

                expression.execute(params);
            });

            Thread.sleep(5);
        }
    }

    private static Expression compile() {
        String script = "fn func13958cf27099476b3c208d422dbfef3c() {\n" +
                "    let $field1 = nil;\n" +
                "\n" +
                "    let isMatched = ($field2 >= 3);\n" +
                "    if(isMatched == true){\n" +
                "        $field1 = 'D940';\n" +
                "    }\n" +
                "\n" +
                "    return seq.map('$field1', $field1, '$field2', $field2);\n" +
                "}\n" +
                "\n" +
                "func13958cf27099476b3c208d422dbfef3c()";

        return AviatorEvaluator.getInstance().compile(script, false);
    }
}

涉及脚本

fn func13958cf27099476b3c208d422dbfef3c() {
    let $field1 = nil;

    let isMatched = ($field2 >= 3);
    if(isMatched == true){
        $field1 = 'D940';
    }

    return seq.map('$field1', $field1, '$field2', $field2);
}

func13958cf27099476b3c208d422dbfef3c()

实际使用是Dubbo调用,所以这里用线程池去模拟Dubbo的线程池。我本地dubug断点发现最后thread pool中所有Thread所持有的threadlocalMap都没有释放,里面都是Env即请求的入参信息。

@killme2008
Copy link
Owner

LambdaFunction 里是有 thread local 缓存,缓存 lambda 实例和上一次调用的 context,这个理论上没有影响的,每次调用都会被替换掉。用你的例子去跑,观察 gc 也是完全正常的,并不会 OOM。

@killme2008
Copy link
Owner

这里唯一可能是说你传入的变量,是一个累积型的变量,由于这个 context 缓存,导致变量没有被及时 gc。

@hillfly
Copy link
Author

hillfly commented Jun 15, 2021

已经搞清楚了。
因为线上Dubbo线程池设置了比较大,并且空闲Thread并未配置自动回收。
线上跑着很多脚本,恰恰某些脚本需要的变量数很多(需要80个左右的变量),在请求量大的情况下,Thread中持有的Env基本都是这些变量,占用了很大一部分内存。

我这边配置线程池为cached,自动回收空闲线程后,这部分内存就释放了。
不过我依旧建议,ThreadLocal这块信息,调用后是否就可以remove了,或者解除跟Env的引用。

@killme2008
Copy link
Owner

ok,收到你的建议,确实在这种情况下可能引起不必要的内存占用,我看看怎么解

@hillfly
Copy link
Author

hillfly commented Jun 19, 2021

hi. 我这边尝试clone到本地后修改源码,execute后释放这部分内存占用。
我看了下,是否可以在ClassExpression的executeDirectly方法中加finaly处理,遍历lambdaBootstraps,并remove fnLocal
但是这样实现感觉很粗暴,遍历的操作也有点臃肿,不知道作者是否有别的妙计

@killme2008
Copy link
Owner

我用了另一个种办法解决,今天会发布 5.2.6 分支,您可以帮忙测试下

#376

@hillfly
Copy link
Author

hillfly commented Jun 22, 2021

OK。 我check一下。

@hillfly
Copy link
Author

hillfly commented Jun 22, 2021

5.2.6 tag本地test没问题。请问今天什么时候发新release,到时候我放预生产环境试试

@killme2008
Copy link
Owner

@hillfly 已发布到 oss,明天应该可以在 maven central 看到

@killme2008
Copy link
Owner

已发布 5.2.6,推荐升级

https://github.com/killme2008/aviatorscript/releases/tag/aviator-5.2.6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants