我该如何修复这个内存泄漏?

时间:2015-01-24 12:28:54

标签: java memory-management memory-leaks inputstream

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。从那时起,它就不会再停止下降了。

3 个答案:

答案 0 :(得分:0)

为什么要首先清除地图?如果您打算将存档加载器实例重新用于其他存档,那只会有意义。为什么不只是实例化另一个实例,如果你想这样做,并且一旦你没有引用它就让GC自动清除它?在使用Java进行编码时,您似乎正在尝试进行过多的内存管理。我不认为你首先有内存泄漏,就像鲍里斯所说,内存使用率不会立即下降。如果在反复加载存档后实际上内存不足,那么您就会知道存在内存泄漏。否则,分析就不那么简单了。

答案 1 :(得分:0)

  问:是否有内存泄漏?

我不相信你有内存泄漏。事实上,如果可达数据的数量变化很大,我认为如果不泄漏的程序就会出现这种情况。

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

要确定您是否有真正的内存泄漏(在Java堆中),您需要查看一段时间内的最小和最大堆使用情况。具体来说,您需要在运行GC之前和之后获取“已使用”和“自由”值....在多个GC循环中。如果这些值(在这些点)随着时间的推移有明显的上升趋势,那么你就会遇到问题。

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

简单的方法是使用此处所述的Oracle visualvm工具。内存使用情况图看起来像一个锯齿,其中“peak”和“valley”对应于垃圾收集。您正在寻找的是高峰和低谷的长期向上趋势。

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

  问:那么为什么Windows说你使用了这么多内存?

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

  • 它不知道您的应用程序将要执行的操作。它不知道你的应用程序要求多少内存,什么时候发布它以及它是否会要求它回来。

  • 它只在运行GC时“做某事”,而且只有当“空间”变满或接近满时才会发生。 (这不太可能与您的clear()来电相对应。)

  • JVM将未使用的内存返回给操作系统会产生很大的成本。需要移动对象,以便可以调整“空格”的大小而不会破坏地址空间。

所以这意味着如果你有一个具有“突发”内存需求配置文件的应用程序,那么JVM很可能会调整堆的大小以保持最大需求,并且长期保持在该级别。

这并不是说JVM永远不会回忆内存。根据JVM堆调整参数,如果JVM在多个GC周期后发现堆太大,它将通过返回内存来减少堆。然而,它以保守/不情愿的方式做到这一点。不情愿的原因是:

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

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

  

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

没有!没有!不!

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

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

  

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

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

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

答案 2 :(得分:0)

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

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

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

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

但是,在您的情况下,代码将ZipInputStream中的FileInputStream包装起来,代码关闭三次,通常只关闭ZipInputStream。

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