Java中Groovy执行器内存限制实现方法咨询
Groovy脚本内存限制实现方案
针对你在Java环境中运行Groovy脚本时的内存限制需求,以下是几种可行的实现方式,结合你已有的代码框架:
方式一:线程内存监控+中断(简单直接)
将脚本执行逻辑放到单独线程中,启动监控线程定时检查堆内存占用,达到阈值时中断脚本线程:
示例代码:
// 执行脚本的线程 Thread scriptThread = new Thread(() -> { try { // 执行Groovy脚本逻辑,例如调用脚本的执行方法 groovy.invokeMethod("execute", null); } catch (InterruptedException e) { throw new RuntimeException("脚本内存占用超过阈值", e); } }); // 内存监控线程 Thread monitorThread = new Thread(() -> { long memoryThreshold = 1024 * 1024 * 50; // 设置50MB内存阈值 while (scriptThread.isAlive()) { // 计算当前已使用堆内存 long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); if (usedMemory >= memoryThreshold) { scriptThread.interrupt(); break; } try { Thread.sleep(100); // 每100ms检查一次内存 } catch (InterruptedException ignored) {} } }); // 启动线程 scriptThread.start(); monitorThread.start(); // 等待脚本执行完成 try { scriptThread.join(); monitorThread.interrupt(); monitorThread.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
方式二:通过Groovy Sandbox拦截内存相关操作(精准控制)
利用已集成的groovy-sandbox自定义拦截器,监控脚本中的变量赋值、对象创建、集合添加等操作,累计内存占用,达到阈值时终止脚本:
示例代码:
// 自定义内存监控拦截器 class MemoryLimitInterceptor extends SandboxInterceptor { private final long memoryThreshold; private long initialUsedMemory; public MemoryLimitInterceptor(long memoryThreshold) { this.memoryThreshold = memoryThreshold; // 记录脚本执行前的内存占用 this.initialUsedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } @Override public Object onSetProperty(GroovyObject obj, String property, Object value) throws Throwable { checkMemoryLimit(); return super.onSetProperty(obj, property, value); } @Override public Object onNewInstance(Constructor c, Object[] args) throws Throwable { checkMemoryLimit(); return super.onNewInstance(c, args); } @Override public Object onMethodCall(Object receiver, String method, Object[] args) throws Throwable { // 拦截集合的add/addAll操作,防止无限扩容 if (receiver instanceof Collection && ("add".equals(method) || "addAll".equals(method))) { checkMemoryLimit(); Collection coll = (Collection) receiver; if (coll.size() >= 10000) { // 限制集合最大元素数 throw new RuntimeException("集合大小超过限制"); } } return super.onMethodCall(receiver, method, args); } private void checkMemoryLimit() { long currentUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); long scriptUsed = currentUsed - initialUsedMemory; if (scriptUsed >= memoryThreshold) { throw new RuntimeException("脚本内存占用超过阈值"); } } } // 替换原有SandboxInterceptor注册逻辑 // 注释掉 new SandboxInterceptor().register(); new MemoryLimitInterceptor(1024 * 1024 * 50).register(); // 传入50MB阈值
方式三:结合SecureASTCustomizer限制危险操作(提前预防)
通过SecureASTCustomizer禁止或限制可能导致内存泄漏的操作,比如禁止无限循环、限制集合初始化大小:
示例代码:
final SecureASTCustomizer secure = new SecureASTCustomizer(); // 保留你原有的配置... // 禁止while循环,防止无限循环赋值 secure.setTokensBlacklist(concatList(new Integer[0], TOKEN_BLACK_LIST, TokenType.WHILE)); // 限制集合初始化的元素数量 secure.addExpressionCheckers(expression -> { if (expression instanceof ConstructorCallExpression) { ConstructorCallExpression cce = (ConstructorCallExpression) expression; if ("java.util.ArrayList".equals(cce.getType().getName()) && cce.getArguments() != null) { if (cce.getArguments().size() > 1000) { throw new RuntimeException("集合初始化元素过多"); } } } return true; });
注意事项
- 线程中断方式需要脚本代码能响应中断,比如在循环中主动检查
Thread.currentThread().isInterrupted(),否则无法及时终止 - Sandbox拦截方式需覆盖所有可能消耗内存的操作(如数组创建、字符串拼接等),才能做到精准监控
- 内存阈值需根据JVM堆大小调整,避免误判
内容的提问来源于stack exchange,提问作者Fate
相关产品推荐
相关产品推荐

