我该如何修复此内存泄漏?

How can I fix this memory leak?

import java.io.*;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ArchiveLoader {

    private static final Logger logger = Logger.getLogger(Landing.class.getName());

    private final String PREFIX = ".class";
    private final byte[] BUFFER = new byte[1024];

    private File archive;

    private HashMap<String, byte[]> classMap = new HashMap<>();

    public ArchiveLoader(String archivePath) throws IOException {
        this.archive = new File(archivePath);
    }

    public void load() throws IOException {
        FileInputStream fis = new FileInputStream(archive);
        loadStream(fis);
        fis.close();
    }

    private void loadStream(InputStream inputStream) throws IOException {
        if (archive.canRead()) {
            if (classMap.size() == 0) {
                ZipInputStream zis = new ZipInputStream(inputStream);

                ZipEntry entry;
                while ((entry = zis.getNextEntry()) != null) {
                    String name = entry.getName();
                    if (name.toLowerCase().endsWith(PREFIX)) {
                        name = name.substring(0, name.indexOf(PREFIX));
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        int read;
                        while ((read = zis.read(BUFFER, 0, BUFFER.length)) != -1) {
                            bos.write(BUFFER, 0, read);
                        }
                        zis.closeEntry();
                        bos.close();
                        classMap.put(name, bos.toByteArray());
                    }
                }

                inputStream.close();
                zis.close();
                logger.info("Loaded " + classMap.size() + " classes.");
            } else {
                logger.info("Archive has already been loaded!");
            }
        } else {
            throw new IOException("Could not read the JAR archive.");
        }
    }

    public void clear() {
        classMap.clear();
        System.out.println(classMap.size());
        classMap = null;
        logger.info("`enter code here`Cleared the ArchiveLoader.");
    }

}

如何再次降低内存使用量?

        for (int i = 0; i < 10; i++) {
            ArchiveLoader archiveLoader = new ArchiveLoader(FileManager.getClientLocation());
            archiveLoader.load();
            archiveLoader.clear();
        }

当我运行这个时,内存使用量一路上升到660mb 然后减少到 526mb。从那时起,它就不会再停止下降了。

你为什么要首先清除地图?如果您打算将存档加载器实例重新用于另一个存档,那才有意义。如果你想这样做,为什么不直接实例化另一个实例,一旦你不再引用它,让 GC 自动清除这个实例?在 Java 中编码时,您似乎尝试进行过多的内存管理。我不认为你首先有内存泄漏,就像鲍里斯说的那样,内存使用量不会立即下降。如果你在一遍又一遍地加载档案后实际上 运行 内存不足,那么你就会知道你有内存泄漏。不然分析没这么简单

Q: Is there a memory leak?

我完全不相信你有内存泄漏。事实上,如果可访问的数据量变化很大,这看起来是我期望非泄漏程序的行为方式。

首先,您正在查看 OS 报告的内存使用情况。这包括 JVM 使用的所有内存,包括各种堆外资源,例如本机库和堆栈。它还包括堆开销,例如 evacuated spaces;例如计入堆 "free space".

的内存

要确定是否存在真正的内存泄漏(在 Java 堆中),您需要查看一段时间内的最小和最大堆使用量。具体来说,您需要在 GC 之前和之后获取 "used" 和 "free" 值 运行 .... 在多个 GC 周期中。如果随着时间的推移这些值(在那些点)有明显的上升趋势,那么你就有问题了。

Q: How do you get hold of that information?

简单的方法是使用此处描述的 Oracle visualvm 工具。内存使用图看起来像锯齿波,其中 "peaks" 和 "valleys" 对应于垃圾回收。你要找的是峰谷高度的长期上升趋势。

如果 visualvm 提供了(真实的)泄漏证据,那么它还有工具可以帮助您追踪它们。

Q: So why does Windows say you are using so much memory?

好吧,基本上你正在使用那个内存。 JVM 向 OS 请求足够的内存以使堆尽可能大以容纳所有对象。在您的情况下,"demand" 在两个极端之间波动。 JVM 的问题如下:

  • 它不知道你的应用程序要做什么。它不知道您的应用程序要请求多少内存,何时释放它以及是否要请求它。

  • 它仅在 运行 成为 GC 时才 "does something",并且只有当 "space" 已满或接近满时才会发生这种情况。 (这不太可能对应于您的 clear() 调用。)

  • JVM 将未使用的内存返还给 OS 的成本很高。需要四处移动对象,以便可以调整 "spaces" 的大小而不会碎片化地址 space.

所以这意味着如果您的应用程序具有 "bursty" 内存需求配置文件,JVM 可能会调整堆的大小以容纳最大需求,并在很长一段时间内将其保持在该级别任期。

这并不是说 JVM 从不归还内存。根据 JVM 堆调整参数,如果 JVM 在多次 GC 周期后发现堆太大,它会通过返还内存来减少堆。但是,它以保守/不情愿的方式这样做。不愿意的原因是:

  • 如果堆很大,垃圾收集效率更高。

  • (再次)增加堆是它想要避免的开销。

Q: Should you run System.gc() ?

没有!不!没有!

可以强制GC到运行(如上),但是对于系统性能来说,让JVM决定什么时候高效 到它。

此外,您无法保证 运行 GC 会导致 JVM 将任何内存返回给 OS ... 如果您的目标是减少内存使用系统级。

Q: How do I use as little (memory) resources as possible.

  1. 使用 C 或 C++ 等非托管语言重写您的应用程序,并实现您自己的内存管理。

  2. 不要像那样在内存中缓存 JAR 文件内容。

我希望其他答案能提供保证w.r.t。内存泄漏和 JVM 行为。

您的程序仍然可能会泄漏 - 出现异常(但在日志中显示)。然而,这是一件小事。

使用 try-with-resources 来防止异常等资源泄漏。

try (FileInputStream fis = new FileInputStream(archive)) {
    loadStream(fis);
} // Always closes fis.

但是,在您的情况下,代码将 FileInputStream 包装在 ZipInputStream 中,并且代码关闭了三次,而通常一次只会关闭 ZipInputStream。

重新设计,loadStream() 使用 this.archive 似乎最好,使用 try-with-resources 关闭 ZipInputStream。