繁体   English   中英

如何解决此内存泄漏?

[英]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.");
    }

}
  • 在加载JAR存档之前,内存使用量约为14mb。
  • 当我用该类加载jar文件时,内存使用量约为210mb。
  • 当我打电话清除时,内存使用不会减少。

如何再次减少内存使用量?

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

当我运行此命令时,内存使用率一直上升到660mb,然后下降到526mb。 从那时起,它将不再停止下降。

为什么首先要清除地图? 仅当您打算将归档加载程序实例重新用于另一个归档时,才有意义。 如果不想这样做,为什么不立即实例化另一个实例,一旦不再引用它,让GC自动清除它呢? 似乎您正在尝试执行过多的内存管理,而不是用Java进行编码时所需的。 我不认为您一开始就存在内存泄漏,就像鲍里斯(Boris)所说的那样,内存使用率不会立即下降。 如果一遍又一遍地加载档案后,实际上内存不足,那么您就会知道内存泄漏。 否则,分析就不是那么简单。

问:是否存在内存泄漏?

我完全不相信您有内存泄漏。 实际上,如果可访问的数据量高度可变,这看起来将使我期望非泄漏程序的行为。

首先,您正在查看操作系统报告的内存使用情况。 这包括JVM使用的所有内存,包括各种堆外资源,例如本机库和堆栈。 它还包括堆开销,例如撤离空间; 例如,内存计入堆的“可用空间”。

为了确定您是否有实际的内存泄漏(在Java堆中),您需要查看一段时间内的最小和最大堆使用情况。 具体来说,您需要在多个GC周期内运行GC之前和之后保持“已使用”和“免费”值。 如果这些值(在这些时间点)随着时间呈明显的上升趋势,那么您就有问题了。

问:您如何掌握这些信息?

简单的方法是使用此处所述的Oracle visualvm工具。 内存使用情况图看起来像是锯齿形的,具有与垃圾回收相对应的“峰值”和“谷”。 您正在寻找的是峰谷高度的长期上升趋势。

如果visualvm提供了(真实的)泄漏证据,那么它也提供了帮助您进行跟踪的工具。

问:那为什么Windows会说您使用了那么多内存?

好吧,基本上,您正在使用该内存。 JVM要求操作系统提供足够的内存,以使堆达到容纳所有对象所需的大小。 在您的情况下,“需求”在两个极端之间波动。 JVM的问题如下:

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

  • 它仅在运行GC时“执行某些操作”,并且仅在“空间”变满或接近充满时才会发生。 (这不太可能与您的clear()调用相对应。)

  • JVM将未使用的内存返还给OS的成本很高。 需要移动对象,以便可以调整“空间”的大小而不会分散地址空间。

因此,这意味着如果您的应用程序具有“突发”的内存需求配置文件,则JVM可能会调整堆大小以容纳最大需求,并从长远来看将其保持在该级别。

这并不是说JVM从不回馈内存。 根据JVM堆调整参数的不同,如果JVM在多个GC周期后发现堆太大,则会通过返还内存来减少堆。 但是,它以保守/勉强的方式执行此操作。 不愿意的原因是:

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

  • 再次增加堆是要避免的开销。

问:您应该运行System.gc()吗?

没有! 没有! 没有!

可以强制GC运行(如上所述),但是将系统的性能留给JVM来决定何时对它有效是更好的系统性能。

此外,如果您的目标是减少系统级别的内存使用量,则不能保证运行GC会导致JVM将任何内存返还给OS。

问:如何使用尽可能少的(内存)资源。

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

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

我希望其他答案可以保证内存泄漏和JVM行为。

仍然您的程序可能会泄漏-例外情况(但是在日志中显示)。 可是一件小事。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM