2016-06-02 8 views
3

Ich habe für BufferedReader Wrapper bekam, die eine nach der anderen in Dateien liest einen ununterbrochenen Strom über mehrere Dateien zu erstellen:Warum codiert mein BufferedReader Speicherverlust?

import java.io.BufferedReader; 
import java.io.FileInputStream; 
import java.io.FileReader; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.Reader; 
import java.util.ArrayList; 
import java.util.zip.GZIPInputStream; 

/** 
* reads in a whole bunch of files such that when one ends it moves to the 
* next file. 
* 
* @author isaak 
* 
*/ 
class LogFileStream implements FileStreamInterface{ 
    private ArrayList<String> fileNames; 
    private BufferedReader br; 
    private boolean done = false; 

    /** 
    * 
    * @param files an array list of files to read from, order matters. 
    * @throws IOException 
    */ 
    public LogFileStream(ArrayList<String> files) throws IOException { 
     fileNames = new ArrayList<String>(); 
     for (int i = 0; i < files.size(); i++) { 
      fileNames.add(files.get(i)); 
     } 
     setFile(); 
    } 

    /** 
    * advances the file that this class is reading from. 
    * 
    * @throws IOException 
    */ 
    private void setFile() throws IOException { 
     if (fileNames.size() == 0) { 
      this.done = true; 
      return; 
     } 
     if (br != null) { 
      br.close(); 
     } 
     //if the file is a .gz file do a little extra work. 
     //otherwise read it in with a standard file Reader 
     //in either case, set the buffer size to 128kb 
     if (fileNames.get(0).endsWith(".gz")) { 
      InputStream fileStream = new FileInputStream(fileNames.get(0)); 
      InputStream gzipStream = new GZIPInputStream(fileStream); 
      // TODO this probably needs to be modified to work well on any 
      // platform, UTF-8 is standard for debian/novastar though. 
      Reader decoder = new InputStreamReader(gzipStream, "UTF-8"); 
      // note that the buffer size is set to 128kb instead of the standard 
      // 8kb. 
      br = new BufferedReader(decoder, 131072); 
      fileNames.remove(0); 
     } else { 
      FileReader filereader = new FileReader(fileNames.get(0)); 
      br = new BufferedReader(filereader, 131072); 
      fileNames.remove(0); 
     } 
    } 

    /** 
    * returns true if there are more lines available to read. 
    * @return true if there are more lines available to read. 
    */ 
    public boolean hasMore() { 
     return !done; 
    } 

    /** 
     * Gets the next line from the correct file. 
     * @return the next line from the files, if there isn't one it returns null 
     * @throws IOException 
     */ 
    public String nextLine() throws IOException { 
     if (done == true) { 
      return null; 
     } 
     String line = br.readLine(); 
     if (line == null) { 
      setFile(); 
      return nextLine(); 
     } 
     return line; 
    } 
} 

Wenn ich dieses Objekt auf einer großen Liste von Dateien (300 MB im Wert von Dateien) konstruieren , dann nextLine() immer wieder in einer while-Schleife drucken Leistung kontinuierlich verschlechtert, bis es nicht mehr RAM zu verwenden ist. Dies geschieht auch, wenn ich Dateien mit ~ 500 KB einlese und eine virtuelle Maschine mit 32 MB Speicher verwende.

Ich möchte diesen Code in der Lage sein, auf positiv massiven Datensätzen (Hunderte von Gigabytes Dateien) zu laufen, und es ist eine Komponente eines Programms, das mit 32 MB oder weniger Arbeitsspeicher ausgeführt werden muss.

Die Dateien, die verwendet werden, sind meist beschriftete CSV-Dateien, daher die Verwendung von Gzip, um sie auf der Festplatte zu komprimieren. Dieser Reader muss mit gzip und unkomprimierten Dateien umgehen.

Korrigieren Sie mich, wenn ich falsch liege, aber sobald eine Datei gelesen wurde und ihre Zeilen die Daten aus dieser Datei ausgespuckt haben, sollten die mit dieser Datei verbundenen Objekte und alles andere für die Garbage Collection geeignet sein?

+3

Ist das relevant 'C++' überhaupt finden? – Galik

+1

Sie könnten 'fileNames.addAll (files);' in Ihrem Konstruktor verwenden. – Kayaman

+0

Ich würde einen Heap-Dump betrachten, um zu sehen, wo der Speicher beibehalten wird. Was du sagst, scheint das Problem irgendwo anders in deinem Code zu sein. –

Antwort

-1

Der GC beginnt zu arbeiten, nachdem Sie Ihre Verbindung/Leser geschlossen haben. Wenn Sie Java 7 oder höher verwenden, sollten Sie in Betracht ziehen, die Try-with-Resource-Anweisung zu verwenden, die eine bessere Möglichkeit bietet, mit dem IO-Betrieb umzugehen.

0

Der letzte Aufruf von SetFile wird Ihren BufferedReader nicht schließen, so dass Sie Ressourcen verlieren.

Tatsächlich in nextLine lesen Sie die erste Datei bis zum Ende. Wenn das Ende erreicht ist, rufen Sie setFile auf und prüfen Sie, ob weitere Dateien zu verarbeiten sind. Wenn jedoch keine weitere Datei vorhanden ist, kehren Sie sofort zurück, ohne den letzten BufferReader-Benutzer zu schließen.

Darüber hinaus, wenn Sie nicht alle Dateien verarbeiten, haben Sie eine Ressource noch in Verwendung.

+0

Sie haben Recht. Ich habe das behoben, aber es ist nicht mein Problem, denn ich erstelle immer nur eines dieser Objekte und lese hunderte von Dateien durch. – Isaak

+0

Das Leck könnte sich irgendwo anders in Ihrem Code befinden. – JEY

0

Es gibt mindestens ein Leck in Ihrem Code: Methode setFile() nicht die letzten BufferedReader nicht schließen, weil die if (fileNames.size() == 0) Prüfung vor if (br != null) Prüfung kommt.

Dies könnte jedoch nur dann zum beschriebenen Effekt führen, wenn LogFileStream mehrfach instanziiert wird.

Es wäre auch besser zu verwenden LinkedList anstelle von ArrayList als fileNames.remove(0) ist mehr "teuer" auf der ArrayList als auf der LinkedList. Sie könnten instanziiert es im Konstruktor folgende Zeile ein: fileNames = new LinkedList<>(files);

0

jeder einmal in eine Weile, man konnte flush() oder close() die BufferedReader. Dies löscht den Inhalt des Lesers. Spülen Sie den Leser daher möglicherweise jedes Mal, wenn Sie die Methode setFile() verwenden. Dann, kurz vor jedem Gespräch wie br = new BufferedReader(decoder, 131072), close() die BufferedReader

1

Mit Java 8 hat GZIP Unterstützung von Java-Code verschoben nativen zlib Nutzung.

Nicht geschlossene GZIP-Streams leak nativen Speicher (ich sagte wirklich "native" nicht "Heap" Speicher) und es ist weit von leicht zu diagnostizieren. Abhängig von der Anwendungsnutzung solcher Ströme kann das Betriebssystem seine Speichergrenze ziemlich schnell erreichen.

Symptom ist, dass Betriebssystem-Prozess Speichernutzung mit JVM Speichernutzung von Native-Speicher-Tracking produziert nicht konsistent ist https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html

Sie die komplette Geschichte Details bei http://www.evanjones.ca/java-native-leak-bug.html