简体   繁体   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.");
    }

}
  • Before I load my JAR archive the memory usage is around 14mb. 在加载JAR存档之前,内存使用量约为14mb。
  • When I load a jar file with that class the memory usage goes to around 210mb. 当我用该类加载jar文件时,内存使用量约为210mb。
  • When I call clear the memory usage does not decrease. 当我打电话清除时,内存使用不会减少。

How can I decrease the memory usage again? 如何再次减少内存使用量?

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

When I ran this, the memory usage went all the way up to 660mb and then decreased to 526mb. 当我运行此命令时,内存使用率一直上升到660mb,然后下降到526mb。 From that point it wouldn't stop dropping anymore. 从那时起,它将不再停止下降。

Why are you clearing the map in the first place? 为什么首先要清除地图? That would only sort-of make sense if you intend to re-use the archive loader instance for another archive. 仅当您打算将归档加载程序实例重新用于另一个归档时,才有意义。 Why not just instantiate another instance if you want to do that and let this one be cleared automatically by the GC once you have no reference to it anymore? 如果不想这样做,为什么不立即实例化另一个实例,一旦不再引用它,让GC自动清除它呢? It seems you're trying to do too much memory-management than needed when coding in Java. 似乎您正在尝试执行过多的内存管理,而不是用Java进行编码时所需的。 I don't think you have a memory leak in the first place, like Boris said the memory usage won't immediately drop down. 我不认为您一开始就存在内存泄漏,就像鲍里斯(Boris)所说的那样,内存使用率不会立即下降。 If you are actually running out of memory after loading archives over and over, then you'll know you have a memory leak. 如果一遍又一遍地加载档案后,实际上内存不足,那么您就会知道内存泄漏。 Otherwise that analysis isn't this simple. 否则,分析就不是那么简单。

Q: Is there a memory leak? 问:是否存在内存泄漏?

I'm not convinced you have a memory leak at all. 我完全不相信您有内存泄漏。 In fact, this looks how I would expect a non-leaky program to behave if the amount of reachable data was highly variable. 实际上,如果可访问的数据量高度可变,这看起来将使我期望非泄漏程序的行为。

Firstly, you are looking at the memory usage as reported by the OS. 首先,您正在查看操作系统报告的内存使用情况。 This includes all memory used by the the JVM including various out-of-heap resources such as native libraries and stacks. 这包括JVM使用的所有内存,包括各种堆外资源,例如本机库和堆栈。 It also includes heap overheads such as evacuated spaces; 它还包括堆开销,例如撤离空间; eg memory that counts towards the heap's "free space". 例如,内存计入堆的“可用空间”。

To determine if you have a real memory leak (in the Java heap), you need to see the minimum and maximum heap usage over a period of time. 为了确定您是否有实际的内存泄漏(在Java堆中),您需要查看一段时间内的最小和最大堆使用情况。 Specifically, you need to get hold of the "used" and "free" values before and after the GC is run .... over a number of GC cycles. 具体来说,您需要在多个GC周期内运行GC之前和之后保持“已使用”和“免费”值。 If there is a clear upwards trend of these values (at those points) over time, then you have a problem. 如果这些值(在这些时间点)随着时间呈明显的上升趋势,那么您就有问题了。

Q: How do you get hold of that information? 问:您如何掌握这些信息?

The simple way is to use the Oracle visualvm tool as described here. 简单的方法是使用此处所述的Oracle visualvm工具。 The memory usage graph will look like a sawtooth with the "peaks" and "valleys" corresponding to a garbage collection. 内存使用情况图看起来像是锯齿形的,具有与垃圾回收相对应的“峰值”和“谷”。 What you are looking for is a long-term upwards trend in the height of the peaks and valleys. 您正在寻找的是峰谷高度的长期上升趋势。

If visualvm provides (real) evidence of a leak then it also has tools to help you track them down. 如果visualvm提供了(真实的)泄漏证据,那么它也提供了帮助您进行跟踪的工具。

Q: So why does Windows say you are using so much memory? 问:那为什么Windows会说您使用了那么多内存?

Well, basically you ARE using that memory. 好吧,基本上,您正在使用该内存。 The JVM asks the OS for enough memory to make the heap as big as it needs to be to hold all of your objects. JVM要求操作系统提供足够的内存,以使堆达到容纳所有对象所需的大小。 In your case, the "demand" is fluctuating between two extremes. 在您的情况下,“需求”在两个极端之间波动。 The problem for the JVM is as follows: JVM的问题如下:

  • It does not know what your application is going to do. 它不知道您的应用程序将要做什么。 It doesn't know how much memory your application is going to ask for, when it is going to release it and whether it is going to ask for it back. 它不知道您的应用程序需要多少内存,何时释放它以及是否要返回它。

  • It only "does something" when it runs the GC, and that only happens when a "space" gets full, or close to full. 它仅在运行GC时“执行某些操作”,并且仅在“空间”变满或接近充满时才会发生。 (That is unlikely to correspond to your clear() calls.) (这不太可能与您的clear()调用相对应。)

  • There are significant costs for the JVM in giving unused memory back to the OS. JVM将未使用的内存返还给OS的成本很高。 Objects need to be moved around so that "spaces" can be resized without fragmenting the address space. 需要移动对象,以便可以调整“空间”的大小而不会分散地址空间。

So what this means is that if you have an application with a "bursty" memory demand profile, the JVM is likely to size the heap to hold the maximum demands, and leave it at that level in the long term. 因此,这意味着如果您的应用程序具有“突发”的内存需求配置文件,则JVM可能会调整堆大小以容纳最大需求,并从长远来看将其保持在该级别。

This is not to say that the JVM never gives memory back. 这并不是说JVM从不回馈内存。 Depending on the JVM heap tuning parameters, if the JVM sees that the heap is too large after a number of GC cycles, it will reduce it by giving memory back. 根据JVM堆调整参数的不同,如果JVM在多个GC周期后发现堆太大,则会通过返还内存来减少堆。 However, it does this in conservative / reluctant fashion. 但是,它以保守/勉强的方式执行此操作。 The reasons for the reluctance are that: 不愿意的原因是:

  • Garbage collection is more efficient if the heap is large. 如果堆很大,则垃圾收集效率更高。

  • Growing the heap (again) is an overhead that it wants to avoid. 再次增加堆是要避免的开销。

Q: Should you run System.gc() ? 问:您应该运行System.gc()吗?

No! 没有! No! 没有! No! 没有!

It is possible to force the GC to run (as above), but it is much better for system performance to leave it to the JVM to decide when it is efficient to to it. 可以强制GC运行(如上所述),但是将系统的性能留给JVM来决定何时对它有效是更好的系统性能。

Besides, you have no guarantee that the running the GC will lead to the JVM giving any memory back to the OS ... if your goal is to reduce memory usage at the system level. 此外,如果您的目标是减少系统级别的内存使用量,则不能保证运行GC会导致JVM将任何内存返还给OS。

Q: How do I use as little (memory) resources as possible. 问:如何使用尽可能少的(内存)资源。

  1. Rewrite your application is a non-managed language like C or C++ and implement your own memory management. 用非托管语言(如C或C ++)重写应用程序,并实现自己的内存管理。

  2. Don't cache JAR file content in memory like that. 不要在内存中缓存JAR文件内容。

I hope the other answers provided reassurances wrt memory leakage, and JVM behaviour. 我希望其他答案可以保证内存泄漏和JVM行为。

Still your program might leak - on exceptions (shown however in the logs). 仍然您的程序可能会泄漏-例外情况(但是在日志中显示)。 A minor thing however. 可是一件小事。

Use try-with-resources to prevent resource leakage on exceptions and such. 使用try-with-resources可以防止异常等资源泄漏。

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

HOWEVER, in your case the code wraps the FileInputStream in a ZipInputStream, and the code closes three times, where generally one would only close the ZipInputStream. 但是,在您的情况下,代码将FileInputStream包装在ZipInputStream中,并且代码关闭了3次,而通常情况下,仅关闭ZipInputStream。

A redesign, with loadStream() using this.archive would seem best, closing the ZipInputStream using try-with-resources. 使用this.archive使用loadStream()进行重新设计似乎最好,使用try-with-resources关闭ZipInputStream。

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

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