Heap Space Error bei rekursiver Suche in Dateien trotz nur einer Zeile im Speicher

Admiral Helmut

Aktives Mitglied
Hallo liebe Leute,

ich habe ein Problem. Ich schreibe ein Programm dass rekursiv durch Linux Verzeichnisse läuft und alle Dateien öffnet, jede Zeile einliest, prüft ob ein gewisser String enthalten ist und den Reader dann wieder schliesst.

Ich bekomme obwohl ich sogar mit -Xmx2500m den Speicher erhöht habe irgendwann einen Heap Space Error.

Hier habe ich den verantwortlichen Teil des Codes. (das Programm gelangt rekursiv an jedes File im Linux System und führt auf jedes File diesen Code aus.

Java:
if(files[i].isFile()){
			       
			        BufferedReader reader = null;
			        try {
			            reader = new BufferedReader(new FileReader(files[i]));
			            String line;
			            while ((line = reader.readLine()) != null) {
			                if(line.contains(find)){
			                	matches.add(files[i]);
			                }
			            }
			        } catch (Exception e) {
			            e.printStackTrace();
			        } finally {
			            if (reader != null) {
			                try {
			                    reader.close();
			                } catch (IOException e) {
			                    e.printStackTrace();
			                }
			            }
			        }
				}

Ich dachte eigentlich dass ich nur immer eine Referenz auf einen Reader und eine Zeile im Speicher habe. Der Rest müsste doch immer freigegeben werden durch den Garbage Collector. Oder liegt es daran , dass manche Dateien, ewig lange Zeilen haben, bzw der Reader in ihnen keinen Zeilenumbruch erkennt? Aber 2,5GB????

Vielleicht erkennt jemand von euch das Problem.

Falls die Exception von Bedeutung ist:
Code:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:2367)
	at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:535)
	at java.lang.StringBuffer.append(StringBuffer.java:322)
	at java.io.BufferedReader.readLine(BufferedReader.java:363)
	at java.io.BufferedReader.readLine(BufferedReader.java:382)
	at SecureFileSearcherStarter.searchFile(SecureFileSearcherStarter.java:84)
	at SecureFileSearcherStarter.main(SecureFileSearcherStarter.java:22)

Edit: Es hat nichts damit zutun, dass ich vielleicht matches zu groß werden lasse.
Wenn ich ein System.out.println(); in die if-Bedingung mit rein nehme, wird diese sogut wie nie erreicht, (2mal) da ich einen sehr seltenen String suche


Edit2: Könnte es sein, dass ich zu viele Strings anlege? Strings sind ja unveränderlich und es wird mit jeder Zeile ein neues Stringobjekt im Pool gehalten oder? Bleiben diese Strings solange erhalten wie das Programm läuft? Könnte das die Ursache sein?


Vielen Dank für eure Hilfe

Gruß Helmut
 
Zuletzt bearbeitet:

Gucky

Top Contributor
Es kann gut sein, dass kein Zeilenendezeichen dabei ist, zumal das bei Linux, glaube ich, zwei sind. FeedLine und CarriageReturn.
Ließ lieber nur eine bestimmte Anzahl an Zeichen ein (z. B. 80% des zugesicherten Speichers) und arbeite damit. Allerdings musst du dann auch blockübergreifendes Überprüfen implementieren.
 

Admiral Helmut

Aktives Mitglied
Ersteinmal vielen herzlichen Dank für die Antwort.

HHmm da wird mir wohl nichts anderes über bleiben. Du meinst aber trotzdem mit den Standart Methoden des Readers arbeiten oder?

Hast du dir Edit2 von mir durchgelesen? Kannst du dir das vorstellen?

Gruß Helmut
 

rme

Top Contributor
Tipp: Man kann den Inhalt von Dateien effizient lesen, ohne Speicher auf dem Heap dafür zu verwenden. Dazu kann man das Betriebssystem bitten, den Inhalt der Datei in den Speicherbereich eines Prozesses abzubilden - das funktioniert auf tiefer Ebene so, dass sämtliche Zugriffe auf diesen Speicherbereich gewissermaßen virtuell auf die Festplatte umgeleitet werden. Diese Technik nennt sich Memory Mapped I/O.

Dein momentanter Ansatz liest die Datei hingegen dreimal: zuerst fordert der BufferedReader Speicher im Heap an, um die gesamte Datei zu lesen. Dann durchsucht getLine den gelesenen Speicher erneut nach Zeilenumbrüchen, wodurch der gesamte Inhalt nochmal verarbeitet und in einen String kopiert wird.. zuletzt durchsuchst du diesen String nach dem Muster. Stattdessen könnte man auch einfach direkt während des Lesens nach Zeilenumbrüchen oder dem Muster suchen und benötigt dazu überhaupt keinen Speicher. Genau das würde durch das Memory-Mapped-I/O-Vorgehen passieren: Das Betriebssystem kümmert sich von selbst darum, dass die Datei auf tieferer Ebene trotzdem blockweise gelesen wird, sodass es keinen Geschwindigkeitsverlust gibt.

Merke: Wann immer man mit größeren Dateien arbeitet, sollte man diese in den Speicher mappen, statt sie in den Heap zu lesen.

Hier ein Tutorial: Why use Memory Mapped File or MapppedByteBuffer in Java

Weiterer Tipp: Wenn du nach einer Zeichensequenz suchst, ist es unnötig, die Datei zuerst in Zeilen zu zerlegen. Du könntest direkt einen Suchalgorithmus auf den gemappten Speicherbereich anwenden, beispielsweise den Knuth-Morris-Pratt-Algorithmus. Dabei solltest du vermeiden, irgwendwelche Teile der Datei überhaupt in String-Instanzen umzuwandeln, denn dann liest du wieder doppelt (einmal zum String-Konvertieren, dann zum Durchsuchen).
 
Zuletzt bearbeitet:

Admiral Helmut

Aktives Mitglied
Vielen Dank für die Antwort.
Ich werde es mir genauer anschauen.
Meinst du könnte so etwas gehen: (also nach dem Beispiel aus Link1) ich schaue zuerst wie groß ist die Datei.
Dann lege ich dadurch "count fest" und rufe dann wie im Beispiel eine for Schleife auf:

statt:
Java:
 for (int i = 0; i < count ; i++) {
            System.out.print((char) out.get(i));
        }
aber:
Java:
 for (int i = 0; i < count ; i++) {
           if(out.get(i)).contains("string"){}
        }
Der 2te Link ist mir auf die Schnelle fast zu kompliziert :noe:


Edit: Aber das get wird mir immer nur ein Byte liefern oder?

Danke Gruß Helmut
 
Zuletzt bearbeitet:

rme

Top Contributor
Richtig, get wird dir immer nur ein einzelnes Byte liefern, weshalb die Schleife anders arbeiten sollte: Sie könnte beispielsweise bei i = 0 anfangen und darin eine neue Schleife beginnen lassen, die von j = i bis zur Länge deines Musters prüft, ob [i + j] identisch zum Muster an Position j ist. So prüft die innere Schleife, ob das Muster in der Datei an Position i vorkommt. Ist dies nicht der Fall, macht die äußere Schleife eben bei i = 1 weiter und prüft, ob dort das Muster dort vorkommt.

Dies ist bereits ein naiver Suchalgorithmus. Der Nachteil ist, dass nur eine Position in der Datei weiter gerückt wird, wenn das Muster nicht gefunden wurde. Eine Verbesserung ist der oben erwähnte Algorithmus, bei dem vorher berechnet wird, wie weit bei einer Nicht-Übereinstimmung vorwärts gesprungen werden darf.
 
Zuletzt bearbeitet:

Admiral Helmut

Aktives Mitglied
ok das verstehe ich. Und die Prüfung direkt auf dem get bewirkt, dass mir nichts in den Speicher geladen wird?

Edit: Die Zeit wäre nicht mal wichtig. Das würde ich hinbekommen mit nur einem Byyte verutschen.
 
Zuletzt bearbeitet:

rme

Top Contributor
Genau, solange du nur get verwendest, wird das Programm überhaupt keinen Speicher benötigen, das Betriebssystem kümmert sich um diese Details.

Da es dir möglicherweise nur um die Lösung der Aufgabe und nicht um eine elegante Implementierung geht: Unter Linux kannst du auch einfach "grep -R <muster> <verzeichnis>" benutzen, um rekursiv alle Dateien nach einer Zeichenkette zu durchsuchen, z.B. so: "grep -R telnet /etc" durchsucht alle Dateien, die irgendwo innerhalb /etc oder beliebig verschachtelten Unterverzeichnissen davon vorkommen, nach der Zeichenkette "telnet" und zeigt dir alle Dateinamen und zusätzlich die vorkommende Zeile an, wenn es eine Übereinstimmung gibt. Mit dem Paramter -i wird dabei auch die Groß-/Kleinschreibung ignoriert.
 
Zuletzt bearbeitet:

Admiral Helmut

Aktives Mitglied
Nochmal vielen Dank für deine Hilfe.

Es geht schon vorallem um eine Lösung. Allerdings habe ich mittlerweile ein recht umfangreiches Programm geschrieben. Mit Grafik Logging etc.
In File Namen usw wird auch schon erfolgreich gesucht...

Ist es so, dass jedes Byte, dass ich lese einem Char entspricht und ich die Chars der Reihe nach dann austeste?


Danke Gruß Helmut
 

rme

Top Contributor
Das ist leider etwas komplizierter, allerdings hatte deine erste Idee dasselbe Problem: es gibt verschiedene Arten, Zeichen in Dateien zu speichern und man sieht es einer Datei nicht an, welche Codierung (z.B. ASCII, ISO-8859-1, UTF-8, ...) sie verwendet. Um was für Dateien geht es denn? Und was für Muster sind es? Wenn beides völlig beliebig sein soll, wird es kompliziert.
 

fhoffmann

Top Contributor
Hallo,

ich vermute, dass das Problem in einer Unendlichschleife liegt.
Ein Verzeichnis enthält auch sich selbst (".") und das Oberverzeichnis ("..").
Wenn du eine rekursive Funktion über alle Verzeichnisse in einem Verzeichnis aufrufst und dabei diese beiden speziellen Fälle nicht abfängst, kommst du in eine Unendlichschleife.

Gruß
Fritz
 

Admiral Helmut

Aktives Mitglied
Guten morgen ;)
Vielen Dank euch beiden.
Hmm ich fange keinen Sonderfall ab, ich liste mit dir.listFiles() alles auf und gehe mit einer Forschleife drüber.
Aber das Programm funktioniert einwandfrei wenn man es auf nicht so große Teilbereiche im Dateisystem anwendet. Wenn ich nur die Dateinamen durchsuche funktioniert es auch.

Zu rme: Leider ist es völlig beliebig welche Dateien. Es soll eine Software werden, die in einem Unternehmen unverschlüsselte Verzeichnisse durchsucht, ob in diesen Verzeichnissen Dateien enthalten sind, die wichtig für das Unternehmen sind und in den verschlüsselten Bereich müssen.

Es soll aber nicht professionell werden. Welchen Zeichensatz suche ich den oben? Google sagt mir Default Encoding = Cp1252. Ich denke es würde reichen, wenn man den Standart abdeckt und zumindest in txt Dateien Worte findet. Dies funktioniert mit meinem alten Code. Was meinst du? Wie sollte ich einen Char auf ein Byte prüfen?


Würde so etwas funktionieren mit dem Standart Encoding:

Java:
if((char) out.get(i)=='a');

beziehungsweise könnte ich ja gleich im nächsten Schritt noch ein anderes Encoding ausprobieren. Damit ich zumindest 2-3 abdecke. Nur weiss ich jetzt nicht wie ich den Char auf ein anderes Encoding stelle. Kanns leider gar nicht ausprobieren

Vielen Dank Gruß Helmut
 
Zuletzt bearbeitet:

Admiral Helmut

Aktives Mitglied
habe mal folgende Methode implementiert:

Java:
public static boolean stringIsInFile(File f, String toFind, char[] charArrayToFind){
	
						 int fileSize = f.getSize(); 

						RandomAccessFile memoryMappedFile = new RandomAccessFile(f, "rw");
						MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, count);
						
						for (int i = 0; i < fileSize-(charArrayToFind.length-1) ; i++) {
							
							for(int u = 0; u<charArrayToFind.length;u++){
								if(!(char)out.get(i+u)==charArrayToFind[u]){
									break;
								
								}
								if(u==charArrayToFind.length-1&&(char)out.get(i+u)==charArrayToFind[u]){
								
									return true;
								}
							
							}						
							System.out.print((char) out.get(i));
						}

						return false;	
	}

kann sie leider erst heud abend testen. Was meint ihr?


Gruß Helmut
 
Zuletzt bearbeitet:

arilou

Bekanntes Mitglied
[...]
Dein momentanter Ansatz liest die Datei hingegen dreimal: zuerst fordert der BufferedReader Speicher im Heap an, um die gesamte Datei zu lesen. Dann durchsucht getLine den gelesenen Speicher erneut nach Zeilenumbrüchen, wodurch der gesamte Inhalt nochmal verarbeitet und in einen String kopiert wird.. zuletzt durchsuchst du diesen String nach dem Muster.
  • Afaik liest BufferedReader mitnichten schon zu Beginn die gesamte Datei. Ohne weitere Angabe hat BufferedReader (laut API) eine "default buffer size"; i.A. sind das wohl 64KiB.
  • Da Ram-interne Verarbeitung ca. 1 Mio Mal schneller ist, als von Festplatte zu lesen, ist das anschließende Suchen nach Zeilenumbrüchen und einem Inhaltsmuster nicht sehr kritisch. Dass man sich die Zeilen-Unterteilung sparen kann, ist natürlich richtig.
 

rme

Top Contributor
  • Da Ram-interne Verarbeitung ca. 1 Mio Mal schneller ist, als von Festplatte zu lesen, ist das anschließende Suchen nach Zeilenumbrüchen und einem Inhaltsmuster nicht sehr kritisch. Dass man sich die Zeilen-Unterteilung sparen kann, ist natürlich richtig.

Der Punkt ist, dass das Lesen in den RAM völlig unnötig ist - statt die Datei in den Speicher zu lesen und dort zu suchen, kann direkt während des Lesens gesucht werden. Das ist nicht langsamer, sondern schneller. Der Ansatz mit dem BufferedReader verschwendet also Speicher und ist langsamer... Eine Datei in den RAM zu laden lohnt sich nur, wenn mehrfach auf den Inhalt zugegriffen werden soll. Das ist bei einer simplen Suche aber nicht nötig.
 

Admiral Helmut

Aktives Mitglied
Hallo Leute, vielen Dank für eure Hilfe.

Mit folgendem Code funktioniert es zumindest mit den txt Files.

Java:
public static boolean stringIsInFile(File f, String toFind, char[] charArrayToFind){
	
			
						long fileSize = f.length();
						if(fileSize>Integer.MAX_VALUE-1){
							System.out.println("Datei zu Groß!!!!   "+f.getAbsolutePath());
							return false;
						}
						MappedByteBuffer out = null;
						RandomAccessFile memoryMappedFile = null;
						try{
							memoryMappedFile = new RandomAccessFile(f, "rw");
							out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
						}catch(Exception e){
							e.printStackTrace();
						}
						for (int i = 0; i < fileSize-(charArrayToFind.length-1) ; i++) {
							
							for(int u = 0; u<charArrayToFind.length;u++){
								if(!(((char)out.get(i+u))==charArrayToFind[u])){
									break;
								
								}
								if(u==charArrayToFind.length-1&&(char)out.get(i+u)==charArrayToFind[u]){
									try {
										memoryMappedFile.close();
									} catch (IOException e) {
										// TODO Auto-generated catch block
										e.printStackTrace();
									}
									return true;
								}
							
							}						
							//System.out.print((char) out.get(i));
						}
						try {
							memoryMappedFile.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						return false;	
	}


Vielleicht hat jemand eine Ahnung wie man in ein anderes Charset einliest und dieses auch prüft?



Liebe Grüße Helmut
 
Zuletzt bearbeitet:

arilou

Bekanntes Mitglied
Der Punkt ist, dass das Lesen in den RAM völlig unnötig ist - statt die Datei in den Speicher zu lesen und dort zu suchen, kann direkt während des Lesens gesucht werden. Das ist nicht langsamer, sondern schneller. Der Ansatz mit dem BufferedReader verschwendet also Speicher und ist langsamer... Eine Datei in den RAM zu laden lohnt sich nur, wenn mehrfach auf den Inhalt zugegriffen werden soll. Das ist bei einer simplen Suche aber nicht nötig.
Mal zur Klarstellung:
  • Um in der Datei suchen zu können, müssen die zu durchsuchenden Dateibereiche mindestens 1* ins Ram gelesen werden, auf welchem Weg auch immer. Was du mit "dass das Lesen in den RAM völlig unnötig ist" aussagen willst, ist mir schleierhaft.
  • Über das OS eine Datei "altmodisch" einzulesen, ist wohl meist etwas langsamer als Memory-Mapped-IO; aber solange der Haupt-Zeitbedarf der Zugriff auf die Platte ist (oder eine SSD, ist auch noch "sehr langsam"), spielt das eine untergeordnete Rolle.
  • "Eine Datei in den RAM zu laden lohnt sich nur, wenn mehrfach auf den Inhalt zugegriffen werden soll. Das ist bei einer simplen Suche aber nicht nötig."
    Hä?!? Also zumindest 1* ins Ram Lesen muss geschehen, und das ist der Hauptzeitbedarf der ganzen Aktion, und kann nicht eingespart werden.
  • "Der Ansatz mit dem BufferedReader verschwendet also Speicher und ist langsamer" - richtig, aber im Vergleich zum super-lahmen HDD-Lesen spielt das eine (weit) untergeordnete Rolle.
Ich hoffe mal, du hast dich nur etwas (für mich) missverständlich ausgedrückt, denn sonst wäre deine Antwort einfach nur Murks.

PS: Eine Festplatte ist selbst im Optimalfall ca. 1 Mio mal langsamer als das Ram; selbst eine SSD ist noch 1000 Mal langsamer als Ram, und insofern immernoch "langsam".
 
Zuletzt bearbeitet:

rme

Top Contributor
Um in der Datei suchen zu können, müssen die zu durchsuchenden Dateibereiche mindestens 1* ins Ram gelesen werden, auf welchem Weg auch immer.

Korrekt. Mit Memory-Mapped-IO geschieht dies durch das Betriebssystem und es ist für den Prozess nicht nachvollziehbar, was im Hintergrund passiert. Das Betriebssystem kümmert sich um Details wie Caching und sorgt für performanten Zugriff, solange sie dort gepuffert bleiben.

Was du mit "dass das Lesen in den RAM völlig unnötig ist" aussagen willst, ist mir schleierhaft.

Hier wollte ich die Wortwahl des Thread-Erstellers beibehalten. Präziser meinte ich damit: Es ist unnötig, die Daten, die bereits - auf welchem Weg auch immer - im RAM liegen, erneut in den Heap des Java-Programms zu kopieren. Dadurch ist der Inhalt der Datei doppelt im Speicher: Einmal in irgendeinem Cache des Betriebssystems und einmal im Heap des Java-Prozesses.

Über das OS eine Datei "altmodisch" einzulesen, ist wohl meist etwas langsamer als Memory-Mapped-IO; aber solange der Haupt-Zeitbedarf der Zugriff auf die Platte ist (oder eine SSD, ist auch noch "sehr langsam"), spielt das eine untergeordnete Rolle.

Korrekt. Es ging dem Thread-Ersteller auch gar nicht um die Geschwindigkeit, sondern um den Speicherbedarf des Programms - er bekam ja sogar einen Heap-Overflow! Das liegt daran, dass er in einer Schleife immer wieder neuen Speicher angefordert hat, den er überhaupt nicht benötigt. Wenn man in Java alle Dateien der Festplatte durchsucht und dabei für jede Datei einen neuen BufferedReader instanziiert, bekommt man hier natürlich schnell Probleme durch die doppelte Speicherhaltung. Meine Anmerkungen bezüglich der Geschwindigkeit waren nur zur Aufklärung, dass man auch weitere Vorteile erhält. Aber der Speicherbedarf war das eigentliche Argument.

"Eine Datei in den RAM zu laden lohnt sich nur, wenn mehrfach auf den Inhalt zugegriffen werden soll. Das ist bei einer simplen Suche aber nicht nötig."
Hä?!? Also zumindest 1* ins Ram Lesen muss geschehen, und das ist der Hauptzeitbedarf der ganzen Aktion, und kann nicht eingespart werden.

Das war wohl dasselbe Missverständnis wie oben. Ich meinte hier das Kopieren des bereits durch das Betriebssystem gepufferten Inhalts in den Heap. Und das lohnt eben nur, wenn man häufiger auf die Daten zugreift und sie nicht nur linear durchsucht, denn irgendwann verschwinden die Daten wieder aus dem Puffer des Betriebssystems, sodass eine lokale Pufferung im Heap nötig wird.

"Der Ansatz mit dem BufferedReader verschwendet also Speicher und ist langsamer" - richtig, aber im Vergleich zum super-lahmen HDD-Lesen spielt das eine (weit) untergeordnete Rolle.[/list]Ich hoffe mal, du hast dich nur etwas (für mich) missverständlich ausgedrückt, denn sonst wäre deine Antwort einfach nur Murks.

Es ging, wie gesagt, um den Speicherbedarf und nicht um die Geschwindigkeit. Und diese Lösung hat es dem Thread-Ersteller ermöglicht, den Speichervebrauch von der Situation "Heap-OverFlow trotz 2GB Heap!" zu "Programm benötigt keinen Heap" geführt.. ich finde nicht, dass das Murks ist.
 
Zuletzt bearbeitet:

arilou

Bekanntes Mitglied
Ich bin mir nicht so sicher, ob bei der "BufferedReader"-Lösung ein
"Kopieren des bereits durch das Betriebssystem gepufferten Inhalts in den {Java-}Heap"
geschieht. Afaik beherrscht die Pagetable "copy-on-write", d.h. die Ram-Bereiche, in die das OS den File gelesen hat, werden einfach direkt dem Java-Programm abgegeben (ohne irgendwelche Ram-to-Ram-Kopieraktionen), und nur wenn das Java-Programm in diesen Speicher schreibt, wird dafür eine Kopie für es angelegt (und das OS hat weiterhin das Original).

Für das "Out-of-Heap-space" ist das natürlich egal. Hier würde ich ebenfalls auf ein "kein Zeilenende zu finden" Problem tippen.
 

rme

Top Contributor
Ich habe mir jetzt die Implementierung des BufferedReaders angeschaut: Jede Instanz belegt bei der Erzeung schon vor der Nutzung 8192 Bytes, die auf dem Heap alloziert werden. Eine Copy-on-Write-Implementierung wäre auch gar nicht möglich, ohne dass der BufferedReader intern selbst ein Memory-Mapping erstellen würde, denn nur so könnte man das mit Dateien realisieren. Das ist dem BufferedReader aber wiederum nicht möglich, da er gar nicht weiß, ob der darunterliegende Reader eine FileReader oder etwas anderes ist. Und der FileReader macht das nicht - weil er eben ein FileReader ist.

Weiterhin wächst dieser interne Puffer des BufferedReaders, wenn ein konsekutiver Block aus dem unterliegenden Reader benötigt wird - da wird das Array neu erstellt und dann der ganze bisherige Inhalt via arraycopy in den neuen Speicher kopiert.

Im Übrigen verstehe ich nicht, warum du meinen Vorschlag so angreifst - ich bin auch gegen zu frühe Optimierung, um beispielsweise von O(3n) auf O(2n) zu kommen. Aber der Unterschied zwischen diesen Lösungen ist O(n) vs. O(1), was die Speicherplatzkomplexität betrifft.

Welchen Vorschlag hast du denn stattdessen? n zu limitieren, d.h. das Programm nicht auf binäre Dateien loslassen zu können, weil die möglicherweise kein \n enthalten und deshalb komplett in den Heap gelesen werden, was zu obigem Fehler führte? Ich bin gespannt.
 
Zuletzt bearbeitet:

arilou

Bekanntes Mitglied
Mit dem Copy-on-Write / Pagetable bezog ich mich auf deine Aussage:
Es ist unnötig, die Daten, die bereits - auf welchem Weg auch immer - im RAM liegen, erneut in den Heap des Java-Programms zu kopieren. Dadurch ist der Inhalt der Datei doppelt im Speicher: Einmal in irgendeinem Cache des Betriebssystems und einmal im Heap des Java-Prozesses.
Diese Kopieraktion muss nicht stattfinden, das OS kann seine Ram-Pages einfach (mit Hinweis "Copy-on-Write") in die Pagetable des Java-Heap eintragen.

Dass Java dann intern ggf. weitere/zusätzliche Kopierereien anstellt, ist natürlich durchaus möglich.

Eine Copy-on-Write-Implementierung wäre auch gar nicht möglich, ohne dass der BufferedReader intern selbst ein Memory-Mapping erstellen würde, denn nur so könnte man das mit Dateien realisieren. Das ist dem BufferedReader aber wiederum nicht möglich, da er gar nicht weiß, ob der darunterliegende Reader eine FileReader oder etwas anderes ist. Und der FileReader macht das nicht - weil er eben ein FileReader ist.
Ich kenne die interne Implementierung dieser Reader nicht; sie könnte durchaus Mapping betreiben. Ich halte das aber für unwahrscheinlich, vmtl. wird da kräftig im Ram hin- und her kopiert, da stimme ich dir zu.

Aber der Unterschied zwischen diesen Lösungen ist O(n) vs. O(1), was die Speicherplatzkomplexität betrifft.
Das habe ich nicht verstanden. Was ist hier dein 'n'? Ich sehe nicht, warum eine Implementierung via FileInputStream (da ja auch non-Character-Dateien untersucht werden sollen) eine Dimension mehr Speicherplatz belegen sollte.

Welchen Vorschlag hast du denn stattdessen? n zu limitieren, d.h. das Programm nicht auf binäre Dateien loslassen zu können, weil die möglicherweise kein \n enthalten und deshalb komplett in den Heap gelesen werden, was zu obigem Fehler führte? Ich bin gespannt.
Nun, zum Suchen in binären Dateien soll man FileInputStream verwenden, und keinen BufferedReader darübersetzen, da selbiger Character-Daten erwartet. Man kann dann nicht mehr komfortabel mit
Java:
String line = myBufferedReader.readLine();
if(line.contains("xyz")) { // ...
suchen, sondern muss eben selber direkt in den Bytes nachsehen/suchen.

Insgesamt wollte ich eigentlich nur aussagen, dass eine "herkömmliche" Lösung weder sehr viel langsamer noch deutlich speicherintensiver sein muss, als eine "memory mapped file"-Lösung. Insbesondere wenn der wesentliche Zeitfaktor das Lesen von der Festplatte ist, und ich mir locker 10* Umkopieren im Ram erlauben kann.

Man bedenke:
Maximale Lesegeschwindigkeit von der HDD: 150 MB/s.
Dagegen (maximale) Speicherbandbreite: ca. 30 GB/s. Beim Umkopieren 15 GB/s lesen, 15 GB/s schreiben, ist somit immernoch 100* schneller als die Platte.
Wenn ich die Daten also 10* im Ram rumkopiere, wird mein Programm dadurch 10% langsamer. Man kann natürlich darum kämpfen, diese 10% auf 2% zu drücken, ja.

Aber die tatsächliche Lösung des ursprünglichen Problems ist nicht "wie spare ich mir die Ram-Kopiererei", sondern "wie suche ich in binary-Daten nach einer Zeichenkette". Wie du bereits angesprochen hast - es geht darum, dass eine Datei evtl. keinen '\n' enthält - dieses Problem ist zu lösen. "Memory mapped file" vs. "herkömmliche InputStreams" ist ein Nebenschauplatz, der ein paar wenige Prozente Ausführungszeit bringt.
 

rme

Top Contributor
Diese Kopieraktion muss nicht stattfinden, das OS kann seine Ram-Pages einfach (mit Hinweis "Copy-on-Write") in die Pagetable des Java-Heap eintragen.

Genau das passiert eben nicht - und wäre bei der Verwendung des BufferedReaders auch gar nicht möglich, wie ich oben beschrieben habe. Der BufferedReader "weiß" eben nicht, dass unter ihm ein FileReader ist. Ich habe mir wie gesagt die offizielle Oracle-Implementierung durchgelesen, da passiert nichts magisches. Wenn mit "new" ein Array angelegt wird und danach mit "read" aus der Datei gelesen wird, darf weder die JVM noch das OS einfach Speicherbereiche ummappen.

Das habe ich nicht verstanden. Was ist hier dein 'n'? Ich sehe nicht, warum eine Implementierung via FileInputStream (da ja auch non-Character-Dateien untersucht werden sollen) eine Dimension mehr Speicherplatz belegen sollte.
Die Dimension mehr Platz wird benötigt, wenn man den BufferedReader verwendet. n ist dann die Größe der maximal einzulesenden Datei, da der BufferedReader mit der Dateigröße wächst, wenn man eine Zeile liest.

Du argumentierst immernoch mit der Laufzeit, obwohl es um den Speicherverbrauch ging.. ich denke, wir reden aneinander vorbei und sollten die Diskussion ruhen lassen :) Deine Lösung mit dem FileInputStream ohne BufferedReader sehe ich als gleichwertig zu meinem Vorschlag an.
 
Zuletzt bearbeitet:

Admiral Helmut

Aktives Mitglied
Hallo Leute ;)

nochmal vielen Dank euch beiden für eure Antworten.
Egal wie ihr euch einigt, die Lösung von rme funktioniert für mein Problem wohl sehr gut und wurde abgenommen.

Vorallem hat sie mir gefallen, weil sie mich interessiert hat und ich bis dato nichts wusste über MemoryMapped IO,
trotzdem war es durch das Tutorial von rme leicht umzusetzen.

Auch durch die vielen Erklärungen habe ich einen Haufen gelernt.

Danke

Edit: Was mich noch interessiert hätte: Könnt ihr ausschließen, dass der Heapspace Error nicht dadurch zustande kommt, weil ich zwar immer nur eine String Referenz hatte, aber Strings unveränderlich sind und noch länger im String Pool bleiben und dieser immer größer wird.

Gruß Helmut
 
Zuletzt bearbeitet:

dzim

Top Contributor
Nur so als Anmerkung: Ich habe mal gelernt (früher, vor vielen Jahren :) ), das RandomAccessFile die performanteste Art ist, mit Dateien umzugehen - heute wären das wohl FileChannels. Ich vermute, richtig verwendet, ist diese Art des Zugriffs dann nicht nur performant, sondern auch Speicherschonend. Aber ich muss zugeben, dass ich immer nach dem Tradeoff schaue: Soll es schnell gehen und sind die Files in ihrer Grösse überschaubar, verwende ich immer noch den guten alten BufferedReader für Textdateien...

Etwas verwundern tut mich, das man auf den ersten Blick bei java.nio.Files (u.s.w.) nicht auch gleich mit Channels arbeitet, da man die aber aus den Reader/Writer- bzw. Input/OutputStream-Objekten holen kann, ist das sicher ok...

#edit
Ich hätte wohl erst in die Links schauen soll, das stand ja eigentlich schon drinn... :-/
 
Zuletzt bearbeitet:

Admiral Helmut

Aktives Mitglied
Ok danke auch euch.

So wie es aussieht war der Grund für den heapspace Error ein 4gb großes Image File.
Kann sein dass er dort keine \n erkennt und dadurch die ganzen 4gb einlesen will.

Witzigerweise bekomme ich dort aber auch bei MappedMemory ein Problem:

Bei MappedByteBuffer.map() muss man die filesize in Bytes angeben. Der Wert darf nicht großer als int.MaxValue sein. Ich habe in meiner Lösung eine Prüfung gemacht ob die Größe des Files kleiner ist und überspringe im Notfall das File...

Wie sich herrausstellte hatte ich eben noch dieses große Image im Sys und es wurde immer übersprungen....

Nochmal danke an alle

Gruß Helmut
 

rme

Top Contributor
Das mit den 4GB stimmt in der Tat und ist ein Design-Fehler in Java. Ich dachte, dass sich das mit den 64-Bit VMs erledigt habe, aber so ist es leider nicht.
 
Zuletzt bearbeitet:
Ähnliche Java Themen
  Titel Forum Antworten Datum
M Java Arbeitsspeicherverbrauch, Heap Space error korrigieren? Java Basics - Anfänger-Themen 18
G error wegen heap space Java Basics - Anfänger-Themen 4
M Java heap space Fehlermeldung beheben Java Basics - Anfänger-Themen 3
G Heap Space erhöhen (64bit) Java Basics - Anfänger-Themen 45
D Java Heap Space Probleme Java Basics - Anfänger-Themen 7
S Input/Output Java heap space Java Basics - Anfänger-Themen 8
W Compiler-Fehler "Could not reserve enough space for object heap"... und dann raucht das Programm ab Java Basics - Anfänger-Themen 3
A Java heap space Java Basics - Anfänger-Themen 11
T Out of Memory (Java Heap Space) Java Basics - Anfänger-Themen 9
D java heap space Java Basics - Anfänger-Themen 6
S Java Heap space trotz -Xmx1024 Java Basics - Anfänger-Themen 10
C 'OutOfMemoryError: Java heap space' Java Basics - Anfänger-Themen 5
L heap space, LinkedList umspeichern Java Basics - Anfänger-Themen 15
D java.lang.outofmemoryerror java heap space bei Hashtable Java Basics - Anfänger-Themen 3
neurox java.lang.OutOfMemoryError: Java heap space Java Basics - Anfänger-Themen 18
B java.lang.OutOfMemoryError: Java heap space bei Musikplayer Java Basics - Anfänger-Themen 7
M Java Heap Space durch Übergang von einer Klasse in die ander Java Basics - Anfänger-Themen 3
G warum heap space problem? Java Basics - Anfänger-Themen 6
V warum heap space überlastung Java Basics - Anfänger-Themen 2
M Beadarf ermitteln für Java heap space Java Basics - Anfänger-Themen 4
M Dateien lesen/schreiben und Heap Space Probleme Java Basics - Anfänger-Themen 8
D suchbaum out of heap space Java Basics - Anfänger-Themen 8
R Java heap space Java Basics - Anfänger-Themen 4
S OutOfMemoryError: Java heap space Java Basics - Anfänger-Themen 6
M Java Heap Space während der Laufzeit ändern Java Basics - Anfänger-Themen 2
E fehlermeldung "java heap space" Java Basics - Anfänger-Themen 21
V Ist Off-Heap-Speicher dasselbe wie Stack-Speicher? Java Basics - Anfänger-Themen 2
S Java Client-je nach Heap Size Größe startet Applikation oder nicht Java Basics - Anfänger-Themen 4
KogoroMori21 Stack und Heap Speicher Java Basics - Anfänger-Themen 1
G Min und Max heap Java Basics - Anfänger-Themen 1
F speicherort stack oder heap Java Basics - Anfänger-Themen 1
M Algorithmus Max-Heap? Java Basics - Anfänger-Themen 3
P Stack, Heap Java Basics - Anfänger-Themen 13
S Java memory fehler: Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap spa Java Basics - Anfänger-Themen 5
J Array von Objekten, wie schauts im Heap / Stack aus ? Java Basics - Anfänger-Themen 7
V Heap-Sort Java Basics - Anfänger-Themen 0
M Frage zu Stack und Heap Java Basics - Anfänger-Themen 1
H Heap-Auslasung verdoppelt sich schlagartig Java Basics - Anfänger-Themen 3
H Heap Java Basics - Anfänger-Themen 4
B Stack/Heap Frage Java Basics - Anfänger-Themen 36
C Warning: Type safety: Potential heap pollution via varargs parameter array Java Basics - Anfänger-Themen 5
B OOP Zwei gleichnamige Objekte auf dem heap Java Basics - Anfänger-Themen 4
H Heap Java Basics - Anfänger-Themen 2
B Heap-Speicher wieder freigeben Java Basics - Anfänger-Themen 10
N Heap Dump Java Basics - Anfänger-Themen 23
E ternärer Heap in Array-Form Java Basics - Anfänger-Themen 6
E begrenzung des platzes im heap Java Basics - Anfänger-Themen 4
G Frage zur Heap-Belegung Java Basics - Anfänger-Themen 2
N Applet Heap vergrößern Java Basics - Anfänger-Themen 10
G heap size vergrößern Java Basics - Anfänger-Themen 6
S memory heap problem Java Basics - Anfänger-Themen 9
G Aktuelle Heap-Größe auslesen? Java Basics - Anfänger-Themen 3
G Aus Array einen Heap erstellen Java Basics - Anfänger-Themen 5
D Heap erweitern Java Basics - Anfänger-Themen 3
E Heap Size einstellen Java Basics - Anfänger-Themen 7
J Morgen Java-Klausur. Stack, Heap, Method-Area Java Basics - Anfänger-Themen 2
E wieviele objekte am heap?? Java Basics - Anfänger-Themen 14
N Erste Schritte HSV color space - schwarz und weiß nur anhand von Saturation oder Multiplikator ermitteln Java Basics - Anfänger-Themen 14
P Java SocketException: No buffer space available ==> Netzwerkabsturz Java Basics - Anfänger-Themen 5
S Space Invaders Java Basics - Anfänger-Themen 3
L Steuerzeichen für Space (Leerzeichen)? Java Basics - Anfänger-Themen 3
J Space zwischen 2 Character verkleinern Java Basics - Anfänger-Themen 5
I Exception wird gefangen, aber trotzdem in Error Log? Java Basics - Anfänger-Themen 10
terashy VS Code Project run error Java Basics - Anfänger-Themen 10
monsterherz Circle.java:5: error: <identifier> expected Java Basics - Anfänger-Themen 2
monsterherz error: <identifier> expected Java Basics - Anfänger-Themen 2
R Compiler-Fehler identifier error? Java Basics - Anfänger-Themen 3
N Compiler-Fehler Not a statement Error Java Basics - Anfänger-Themen 7
Jul1n4tor Scanner error bei Eingabe die kein Integer ist Java Basics - Anfänger-Themen 4
richrich99 error: illegal start of expression Java Basics - Anfänger-Themen 10
M error: '.class' expected switch(char) Java Basics - Anfänger-Themen 32
N Compiler-Fehler State Machine - Compiler Error Java Basics - Anfänger-Themen 48
U Interface als PAramter (Vergleich) und ein Error Java Basics - Anfänger-Themen 9
FHEFHJHFJH error: class names, 'summe_bsp', are only accepted if annotation processing is explicitly requested Java Basics - Anfänger-Themen 3
S JavaKara Null Exception Error Java Basics - Anfänger-Themen 4
P Eclipse Karate Framework API Test . Unexpected Error: the trustAnchors parameter must be non-empty Java Basics - Anfänger-Themen 1
H Versteht jemand diesen Codewars Error? Java Basics - Anfänger-Themen 8
J Fehlermeldung: A JNI error Java Basics - Anfänger-Themen 3
Gaudimagspam Compiler Error Java Basics - Anfänger-Themen 3
Eule25 Arbeit mit long und int, Error: integer number too large Java Basics - Anfänger-Themen 2
P Welche Zeile in Tadople gibt einen compiler error? Java Basics - Anfänger-Themen 5
B Methoden if-statement error, FX, Fehlermeldung Java Basics - Anfänger-Themen 6
K Error bei meinem Programm - Hilfe Java Basics - Anfänger-Themen 8
A Scanner-Error Java Basics - Anfänger-Themen 8
Elyt Error: incompatible types Java Basics - Anfänger-Themen 3
I Client ObjectInputStream error... Java Basics - Anfänger-Themen 5
Kirby.exe Alle möglichen Error Möglichkeiten abfangen Java Basics - Anfänger-Themen 33
C error: <identifier> expected Java Basics - Anfänger-Themen 13
S Compiler-Fehler Exception in thread "main" java.lang.Error: Unresolved compilation problem: Java Basics - Anfänger-Themen 6
N Methoden Unerklärliche Error Meldung Java Basics - Anfänger-Themen 3
ZH1896ZH Datentypen Error bei For-Schleife Java Basics - Anfänger-Themen 2
R Error, wenn mehrere Clients gleichzeitig die Verbindung beenden Java Basics - Anfänger-Themen 16
Z Klassen Error: ';' expected - was mache ich falsch? Java Basics - Anfänger-Themen 4
9 Error bei .split() Java Basics - Anfänger-Themen 2
L Operatoren error: bad operand types for binary operator && Java Basics - Anfänger-Themen 8
B cal4j - Error at line 1:Unexpected end of file Java Basics - Anfänger-Themen 0
F Erste Schritte error: cannot find symbol Java Basics - Anfänger-Themen 5
L SQLITE - Syntax error Java Basics - Anfänger-Themen 3
R else without if error Java Basics - Anfänger-Themen 5
A Objekt in Methode zurückgeben, JUnit zeigt Error Java Basics - Anfänger-Themen 2

Ähnliche Java Themen

Neue Themen


Oben