<meta http-equiv="Content-Type" content="text/html; charset=GB18030"><b><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">System / OS / Java Runtime Information</span></b><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">Windows 11 23H2</span></div><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">openjdk 22.0.1 2024-04-16</span></div><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">OpenJDK Runtime Environment (build 22.0.1+8-16)</span></div><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">OpenJDK 64-Bit Server VM (build 22.0.1+8-16, mixed mode, sharing)</span><div style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><br></font></div><div style=""><b><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">Description</span></b></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">com.sun.tools.javac.code.Scope.ScopeListenerList#listeners  is not cleaned up in time, which leads to the accumulation of List and WeakReference when reusing Context, leading to OOM.</span></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b><br></b></font></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Reproduce</b></span><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"></font></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box;" color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif">Using JavacTaskPool to compile the same source code repeatedly, the memory usage rises steadily and cannot be recycled until OOM .</font></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box;" color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><br></font></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Expected Result</b></span></div><div style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif">The heap memory size is stable and runs continuously.</font></div><div style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><br></font></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Actual Result</b></span><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b> </b></font></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box;"><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">Exception in thread "main" java.lang.OutOfMemoryError: Java heap space</div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">        at java.base/java.util.List.of(List.java:1030)</div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">        at ReproduceTest.main(ReproduceTest.java:35)</div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><br></div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Source code for an executable test case</b></div><div style=""><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1">import com.sun.tools.javac.api.JavacTaskPool;

import javax.tools.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

public class ReproduceTest {
    private static final String source = """
            import java.util.function.BiFunction;
            public class LambdaContainer {
                public static BiFunction<Integer, Integer, Integer> getLambda() {
                    return (x, y) -> x + y;
                }
            }
            """;

    public static void main(String[] args) throws URISyntaxException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        JavacTaskPool javacTaskPool = new JavacTaskPool(1);
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        MemoryFileManager memoryFileManager = new MemoryFileManager(fileManager);

        List<?> list;
        do {
            System.gc();
            list = javacTaskPool.getTask(null, memoryFileManager, diagnostics,
                    List.of("-source", "22", "-target", "22", "-encoding", "UTF-8"), null
                    , List.of(new MemoryInputJavaFileObject("LambdaContainer.java", source)), task -> {
                        if (task.call()) {
                            try (memoryFileManager) {
                                return memoryFileManager.getOutputs();
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        return Collections.emptyList();
                    });
        } while (!list.isEmpty());
    }


    static class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
        private final List<MemoryOutputJavaFileObject> outputs = new ArrayList<>();

        // Simulate the cache implementation to speed up the run,
        // repeated runs of the same class compilation will not cause the cache memory usage to keep growing
        private final Map<String, String> binaryNameCache = new HashMap<>();
        private final Map<String, Iterable<JavaFileObject>> fileListCache = new HashMap<>();

        protected MemoryFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        @Override
        public String inferBinaryName(Location location, JavaFileObject file) {
            if (file instanceof BinaryJavaFileObject b) {
                String binaryName = b.getBinaryName();
                if (binaryName != null) {
                    return binaryName;
                }
            }
            return binaryNameCache.computeIfAbsent(location.getName() + file.toString(), k -> super.inferBinaryName(location, file));
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            if (kind == JavaFileObject.Kind.CLASS) {
                var fileObject = new MemoryOutputJavaFileObject(className);
                outputs.add(fileObject);
                return fileObject;
            }
            return super.getJavaFileForOutput(location, className, kind, sibling);
        }

        @Override
        public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
            String key = location.getName() + ":" + packageName + ":" + kinds + ":" + recurse;

            return fileListCache.computeIfAbsent(key, k -> {
                try {
                    return super.list(location, packageName, kinds, recurse);
                } catch (IOException e) {
                    return Collections.emptyList();
                }
            });
        }

        public List<MemoryOutputJavaFileObject> getOutputs() {
            return new ArrayList<>(outputs);
        }

        @Override
        public void close() throws IOException {
            super.close();
            outputs.clear();
        }
    }

    interface BinaryJavaFileObject extends JavaFileObject {
        String getBinaryName();
    }

    static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
        private final String content;

        public MemoryInputJavaFileObject(String uri, String content) throws URISyntaxException {
            super(new URI("string:///" + uri), Kind.SOURCE);
            this.content = content;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return content;
        }
    }

    static class MemoryOutputJavaFileObject extends SimpleJavaFileObject implements BinaryJavaFileObject {
        private final ByteArrayOutputStream stream;
        private final String binaryName;

        public MemoryOutputJavaFileObject(String name) {
            super(URI.create("string:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
            this.binaryName = name;
            this.stream = new ByteArrayOutputStream();
        }

        public byte[] toByteArray() {
            return stream.toByteArray();
        }

        public String getBinaryName() {
            return binaryName;
        }

        @Override
        public InputStream openInputStream() {
            return new ByteArrayInputStream(toByteArray());
        }

        @Override
        public ByteArrayOutputStream openOutputStream() {
            return this.stream;
        }
    }
}</font><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">
</span></pre><div><br></div><pre style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><span style="font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; white-space-collapse: collapse;"><b>Workaround</b></span></pre><pre style=""><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;">1. Through reflection, after each call to task.call(), clean up context's Types.MembersClosureCache#nilScope And the Scope.ScopeListenerList#listeners of each ClassSymbol in com.sun.tools.javac.code.Symtab#classes.</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;"><br></span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;">2. Use agent (</span></font>attachment MemoryLeakFixAgent.java) to change the Scope.ScopeListenerList to the following implementation:</pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">    public static class ScopeListenerList {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        Set<ScopeListener> listeners = Collections.newSetFromMap(new WeakHashMap<>());</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        void add(ScopeListener sl) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">            listeners.add(sl);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        void symbolAdded(Symbol sym, Scope scope) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">            walkReferences(sym, scope, false);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        void symbolRemoved(Symbol sym, Scope scope) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">            walkReferences(sym, scope, true);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        private void walkReferences(Symbol sym, Scope scope, boolean isRemove) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">            for (ScopeListener sl : listeners) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">                if (isRemove) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">                    sl.symbolRemoved(sym, scope);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">                } else {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">                    sl.symbolAdded(sym, scope);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">                }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">            }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">        }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;">    }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;"><br></span></font></pre></pre></div></font></div></div>