簡體   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