使用编译器API的程序存在内存泄漏问题

3
我检测到一段代码中存在严重的内存泄漏问题,该代码用于在运行时编译和运行Java代码。我已经创建了堆转储文件,看起来 com.sun.tools.javac.util.SharedNameTable$NameImpL 是罪魁祸首。
我想知道如何防止 SharedNameTable 占用太多空间。是否有一种方法可以强制释放 SharedNameTable

enter image description here

编译器代码:
public static Object compile(String code) throws IOException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, java.lang.InstantiationException {
    String uuid = UUID.randomUUID().toString();
    File theDir = new File(uuid);

    if (!theDir.exists()) {
        System.out.println("creating directory: " + uuid);
        boolean result = false;

        try{
            theDir.mkdir();
            result = true;
        }
        catch(SecurityException se){
            System.out.println(se);
        }
        if(result) {
            System.out.println("DIR created");
        }
    }
    //Compile
    JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager sjfm = jc.getStandardFileManager(null, null, null);
    JavaFileObject javaObjectFromString = getJavaFileContentsAsString(new StringBuilder(code));
    System.out.println(javaObjectFromString.getName());
    Iterable fileObjects = Arrays.asList(javaObjectFromString);
    String[] options = new String[]{"-d", "/src/"+uuid};
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
    if (jc.getTask(null, null, diagnostics, Arrays.asList(options), null, fileObjects).call()) {
        sjfm.close();
        System.out.println("Class has been successfully compiled");
        //Load
        URL[] urls = new URL[]{new URL("file:///src/"+uuid+"/")};
        URLClassLoader ucl = new URLClassLoader(urls);
        Class cl = ucl.loadClass("TestClass");
        System.out.println("Class has been successfully loaded");
        //Run
        final Method method = cl.getDeclaredMethod("testMethod");
        final Object object = cl.newInstance();
        ExecutorService executor = Executors.newFixedThreadPool(1);
        Callable<Object> task = new Callable<Object>() {
            public Object call() throws IllegalAccessException, InvocationTargetException {
                return method.invoke(object);
            }
        };
        Future<Object> future = executor.submit(task);
        try {
            Object result = future.get(20, TimeUnit.SECONDS);
            return result;
        } catch (TimeoutException ex) {
            return "Method timed out (20 seconds). Please review your code.";
        } catch (InterruptedException e) {
            return "Method interrupted.";
        } catch (ExecutionException e) {
            e.printStackTrace();
            return String.format("ExecutionException thrown! %s", e.getMessage());
        } finally {
            future.cancel(true);
            executor.shutdown();
        }
    }
    sjfm.close();
    System.out.println("Class compilation failed!");
    //Create diagnostics and return them
    List<String> errors = new ArrayList<>();
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()){
        String s = String.format("[Code:%s] %s on line %d / column % d in %s",
                diagnostic.getCode(),
                diagnostic.getKind().toString(),
                diagnostic.getLineNumber(),
                diagnostic.getColumnNumber(),
                diagnostic.getSource());
        errors.add(s);
        errors.add(diagnostic.toString());
    }
    return errors.toArray();
}

编辑:

我找到了一个类似的问题,其中指出SoftReference是问题的原因。 强制发生OutOfMemoryException应该会清除这些内容。 但是,当我尝试在使用“node-java”npm包的node.js应用程序中强制执行OoM时,我的node.js应用程序将在抛出异常之前被终止。


如果您只调用 jc.getTask(null, null, diagnostics, Arrays.asList(options), null, fileObjects).call() 而将 if 语句留空,会发生什么? - Fran Montero
抱歉,我不确定我是否正确理解了您的建议。我尝试在if语句之外运行jc.getTask(null, null, diagnostics, Arrays.asList(options), null, fileObjects).call(),但对内存消耗没有任何影响。 - Hartger
编译任务不是问题。 - Fran Montero
这真的是内存泄漏吗?如果需要释放内存,软引用将被收集。 - biziclop
Node.JS似乎在内存释放之前就出现了故障。我发现Java中有一个已报告的错误,与我正在经历的内存泄漏完全相同(使用javax.tools.JavaCompiler$CompilationTask.call时,SharedNameTableZipFileIndex的大小增加)。 - Hartger
显示剩余2条评论
1个回答

0

正如我在我的回答中所写到这个问题,有一个编译器选项可以完全防止使用SharedNameTable。你只需要将其作为附加编译器选项添加即可:

String[] options = new String[]{"-d", "/src/"+uuid, "-XDuseUnsharedTable"};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接