Effiziente Stringverarbeitung

Huhu,

Ich hab grad dieses "Head First: Java" von Sierra und Bates ausgegraben (Das für Dummies :))

Zitat:

Wenn ihr Java-Programm größer wird, ist es unausweichlich, dass Sie massenhaft String-Objekte haben. Aus Sicherheitsgründen und um Speicherplatz zu sparen, sind Strings in Java unveränderbar.

Das bedeutet, dass folgender Code:

Java:
String s = "0";
for (int x = 1; x<10;x++){
    s = s + x;
}

eigentlich bewirkt, dass Sie 10 String-Objekte (mit den Werten 0, 01, 012 bis 0123456789) erzeugen. Am Ende verweist s auf den String mit dem Wert "0123456789". Aber zu diesem Zeitpunkt gibt es 10 Strings!

Jedes Mal wenn sie einen neuen String machen, steckt die JVM ihn in einen besonderen Speicherbereich, der als String-Pool bezeichnet wird (klingt erfrischend, oder?).
Wenn es im String-Pool bereits einen String mit dem gleichen Wert gibt, erzeugt die JVM keinen zweiten String mit diesem Wert, sondern lässt ihre Referenzvariable einfach auf den vorhandenen Eintrag verweisen.
.
.
.

Die andere Sache mit dem String-Pool ist, dass der Garbage Collector nicht dort hingeht. In unserem Beispiel würden die ersten neun Strings die in unserer for-Schleife erzeugt wurden, für immer nutzlos herumsitzen und Speicherplatz verschwenden, es sei denn, Sie machen später zufälligerweise einen String mit einem gleichen Wert ("01234" beispielsweise).

Wie darf ich das Interpretieren xD?
 
Nach einem Test mit 1463 Durchläufen hat sich das Tool an Platz eins der Speicherverbrauchsliste im Windows TaskManager gefressen. (Mit knapp 42 Mb).

Das ist inakzeptabel.

Der Speicherverbrauch im Task Manager sagt nichts über den Speicher der tatsächlich belegt ist. Da der Garbage Collector ja nicht unbedingt sofort ein Objekt aufräumt wenn es nicht mehr gebraucht wird. Z.B. werden Objekte welche lange leben in einen "höheren" Speicher Pool (es gibt 3) gesteckt und diese werden seltener aufgeräumt. Und wenn der Speicher an die Grenzen geht wird der GC so oder so ausgeführt.

Der aktuell benutzte Heap Speicher kann dir die JConsole mitteilen, welcher im JDK dabei ist.
Gibt es nicht eine Möglichkeit Java zu zwingen nach jedem Durchlauf aufzuräumen?

System.gc() soll man nicht verwenden. Es gab hier schonmal einen längereren Thread diesbezüglich. ;-)
Wenn nämlich keine Durchläufe gemacht werden, sinkt der allocated memory nicht, wieder auf Anfangsniveu.

Ich will jetzt nicht mit dem Finger auf den GarbageCollector zeigen, denn es könnte ja sein, dass ich auch schlampig gearbeitet habe,...

Es hat schon einen Sinn, dass der GC so funktioniert. Bei Linux wird der Speicher auch nicht sofort freigegeben sondern erst nach und nach, je nach Benutzung.
Die StringBuilder die ich erstelle... Was passiert mit denen?

Bei jedem Schleifendurchlauf wird in der Schleife ein neuer Stringbuilder ungefähr so erstellt:

StringBuilder njamnjamnjam = new StringBuilder("Website Website njam njam njam");

Irgendjemand hat mir eingeredet, ich muss die Referenz auf null setzen,...
Stimmt das?

Wird bei in Schleifen definierten Objekten, die ja nach der Schleife nicht mehr accessible sind nicht automatisch abgeräumt?
Ja werden sie, nur halt nicht unbedingt sofort.

Wo sind denn die guten alten Pointer, die besten Freunde des Menschen (noch vor dem Hund) :p?
Objekt a = null;
Hier hast du deinen Pointer a, der auf null zeigt. :-D

Wie darf ich das Interpretieren xD?

Man kann einen String nicht ändern. Sondern wird immer ein neuer String erstellt wird. Darum soll man auch StringBuilder verwenden.

Der String s wird in deinem Beispiel immer durch einen komplett neuen String ersetzt, welches den alten Inhalt von s und x erhählt. Aber es wird nicht einfach x an s angehängt.
 
Ich hab grad dieses "Head First: Java" von Sierra und Bates ausgegraben (Das für Dummies :))

Wie darf ich das Interpretieren xD?

Das haben THMD und ich vorher diskutiert. Dein Buch-Zitat bezieht sich ausschließlich auf Literale. Von einem Stream her jedoch (wie readLine() z.B.) handelt es sich nicht um ein Literal.

In deinem Fall würde ich definitiv nen StringBuilder einsetzen.
 
Hallo,

@miffi - er meint ein anderes Buch.;)

Wie darf ich das Interpretieren xD?
Das auch Bates und Sierra mal nen Fehler machen?

Also vergessen wir mal die ganzen Bücher, Blogs, Lehrer etc. Nehmen wir doch das Sierra und Bates Beispiel. Hier allerdings etwas abgeändert. Der String s wird nicht aufconcateniert sondern in jedem Schleifendurchlauf mit einem neuen String belegt. Das ganze sieht so aus (die auskommentierte intern() Zeile ist erstmal nicht interessant):
Java:
public class StupidString4 {
	public static void main(String[] args) {
		String s="Start";
		long length=0;
		for(int x=1; x<1000000000; x++){
			if(x%1000000==0){
				System.out.println(x);
			}
			s=("blah "+x);
                        //s=("blah "+x).intern();
			length+=s.length();
		}
		System.out.println(length);
	}
}
Nach dem Buch und der alles geht in den Pool Theorie, müssten ja alle Strings die zwischenzeitlich entstehen erhalten bleiben. Ok , dann wird das ganze gestartet und zwar mit folgenden VM-Flags (-Xmx32M -Xms32M -XX:MaxPermSize=32M). Mit diesen Begrenzungen sollte er ja recht bald rausfliegen. Es passiert aber nix - er läuft und läuft und läuft. Wenn man das ganze mal mit der JConsole, JVisualVM, VisualGC, irgend nem Profiler etc. betrachtet, sieht man, dass er fleißig neue Objekte erzeugt und der Garbage Collector regelmäßig abräumt. Ein Speicherproblem wird dadurch aber nicht verursacht.

Ok, dann zwingen wir die Strings mal in den Pool in dem die intern() Zeile aktiviert und entsprechend die andere auskommentiert. Gleiche Einstellungen wie vorher - los gehts. Eigentlich sollten die Strings jetzt in den Pool und die Anwendung krachen - aber nix passiert. Der Pool befindet sich nicht im "normalen" Heap (also Young und Old) sondern im PermSpace der VM. Wenn man den mit dem Tools überwacht sieht man, dass er relativ schnell zuläuft aber beim Erreichen der 32 Megabyte Grenze schlägt der Collector zu und räumt den Perm Space auf.

Und das war mir neu - ich hätte vermutet, dass er bei intern() aussteigt, aber offensichtlich tut er das nicht, zumindest nicht bei der VM, die ich hatte (SunVM 6_u16). Es findet also auch eine Garbage Collection über dem Pool statt.

Damit sind die einzigen Kandidaten, die immer im Pool vorhanden sind, Literale. Und die sollten theoretisch auch im Pool bleiben, da sie ja aus dem Constanten Pool der jeweiligen Klasse kommen, wo sie definiert sind. Sprich solange die Klasse geladen ist, sollte es auf alle Fälle noch eine Referenz drauf geben.


THMD
 
Zuletzt bearbeitet von einem Moderator:
@miffi - er meint ein anderes Buch.;)
Ja, schon klar^^
Ich hatte mich darauf bezogen, dass mccae genau die von uns diskutierte Thematik nochmals aufgegriffen hat. War vielleicht etwas unglücklich formuliert ;)

Es findet also auch eine Garbage Collection über dem Pool statt.
Das finde ich allerdings seltsam... Der Pool wird in diesem Fall offensichtlich bei Speichermangel doch auch geleert. Mir persönlich ist das ebenfalls neu. Ist doch schön, wenn alle was dabei lernen ;)

Und die sollten theoretisch auch im Pool bleiben, da sie ja aus dem Constanten Pool der jeweiligen Klasse kommen, wo sie definiert sind. Sprich solange die Klasse geladen ist, sollte es auf alle Fälle noch eine Referenz drauf geben.
Mir scheint, dass ich bisher doch nur ein eher grundlegendes Wissen bezüglich Strings und die Constant Pools habe.... Hat jede Klasse einen eigenen Pool? Ich dachte, der wäre VM-weit gültig.

Na auf jeden Fall muss ich für das Examen (in 1 - 2 Wochen) wieder auf das oberflächliche und offensichtlich inkorrekte Niveau des Buches absteigen, um die Fragen nach deren Vorstellungen beantworten zu können ;)

Aber nochmal zum Topic:
Die StringBuilder die ich erstelle... Was passiert mit denen?
Bei jedem Schleifendurchlauf wird in der Schleife ein neuer Stringbuilder erstellt
Damit wäre die ganze String-Geschichte eh hinfällig. Offensichtlich benutzt er einen lokalen StringBuilder in der Schleife - das heißt weder im Pool sammeln sich so dermaßen viele Daten, noch bestehen so viele Objekte auf dem Heap... Fragt sich nur, wo die ganze Performance hingeht.
 
@ THMD.

Ich glaubs jetzt, danke.

Btw, erwähnenswert wäre Methode "trimToSize()" beim StringBuilder, die dafür sorgt , dass der allocated Buffer verkleinert wird, solle er zu groß sein.

Und ausserdem, bei mir siehts nicht so aus: string = in.readLine();
Es sieht eher so aus (wie auf der ersten Seite beschrieben):

Java:
		while ((str = in.readLine()) != null) {
			out.append(str);
		}

Wie siehts denn jetzt aus mim Stringpool? :D
 
Moin,

Mir scheint, dass ich bisher doch nur ein eher grundlegendes Wissen bezüglich Strings und die Constant Pools habe.... Hat jede Klasse einen eigenen Pool? Ich dachte, der wäre VM-weit gültig.
ok jetzt bitte nicht durch das Wort Pool verwirren lassen - Mit constant pools meine ich die Bereiche in den class-Files, wo die jeweiligen Konstanten (Literale) hinterlegt werden. Für Strings werden die beim Laden der Klasse in den JVM weiten String Pool gelegt, also keine Panik - im Speicher gibt es natürlich nur einen StringPool.

Na auf jeden Fall muss ich für das Examen (in 1 - 2 Wochen) wieder auf das oberflächliche und offensichtlich inkorrekte Niveau des Buches absteigen, um die Fragen nach deren Vorstellungen beantworten zu können ;)
Naja Bücher sind immer Geschmackssache. Ich fand das Buch eigentlich ziemlich gut. Und keine Angst - Fragen nach dem String Pool und seiner internen Funktionsweise kommen beim SCJP nicht dran (Höchstens im Zusammenhang mit Literalen - aber dass steht ja auch korrekt im Buch). Und die Geschichte mit der Garbage Collection auf dem Pool, wenn Srings über intern() reingelegt wurden ist wohl auch nicht unbedingt in jeder VM enthalten - also verlass dich nicht darauf und benutz intern() nach Möglichkeit nicht bzw. wenn du die Fähigkeiten von intern() benötigst, bau dir eine eigene Lösung nach, die das gleiche macht.

@mccae
Wie siehts denn jetzt aus mim Stringpool?
Prinzipiell gut :). Du liest Zeilenweise über den Reader einen String ein, denn du beim StringBuilder appendest. Der gelieferte Zeilenstring wird nicht mehr referenziert und kann weg - nach der Schleife hast du im Speicher nen StringBuilder, der im Hintergrund ein char-Array hält, wo alles drin steht. Also nix mit StringPool.

Dann zurück zur allgemeinen "Performance". Die VM hat eine bestimmte Standardgröße für ihren maximalen Heap, wenn du das nicht änderst. Bei Windows 32-Bit VM, die standardmäßig im client-Modus gestartet werden ist das 64 MB. Bei Windows 64-Bit VM (oder 32 Bit die mit -server im Servermodus gestartet werden) ist es 1/4 des physikalischen Speichers, maximal 1 GB. Wenn dir also 42 MB zuviel sind - solltest du die Speichergrößen entsprechend begrenzen. Wobei ich bei deiner Anwendung (viele Webseiten analysieren) aus dem Bauch heraus sowieso einen höherern Speicherverbrauch ansetzten würde.

Was ich gar nicht mehr gelesen habe in der ganzen Diskussion ;) - ist denn die Verarbeitungsgeschwindigkeit ausreichend - weil das war ja der ursprüngliche Knackpunkt?

Gruß
THMD
 
also keine Panik - im Speicher gibt es natürlich nur einen StringPool.
Okay :)
Ich taste mich erst langsam an ein tieferes Java-Wissen heran, obwohl ich schon Jahre damit entwickle. Du hast anscheinend wesentlich mehr Ahnung von der Materie - deshalb dachte ich, das wäre schon wieder etwas, wovon ich keinen Plan hab :D

[Gutes Buch] [Fragen zum String-Pool] [intern()]
Das Buch finde ich auch super. Musste auch bei den Sprüchen zwischendurch echt schmunzeln :) Nur offensichtlich sind manche Informationen nur oberflächlich, wo ich einfach gerne einen Hinweis hätte, ob, wie und warum es noch Alternativen gibt.
Bezüglich des String-Pools mache ich mir keine Sorgen beim SCJP. Wie du schon gesagt hast - es handelt sich dabei im Buch um zwar ab und zu oberflächliches, aber dennoch korrektes Wissen. Die intern() Methode habe ich noch nie verwendet und werds vermutlich in naher Zukunft auch nicht... Wenn doch, werd ich deinen Rat beherzigen.


@mccae's Problem:

Also vom bisherigen Standpunkt aus gesehen kannst du von der Performance her nicht viel verbessern. Deine lokalen StringBuilder-Variablen haben ihren Scope und Lebenszeit nur während des Schleifendurchlaufes. Wenn du also nicht gerade eine Member-Variable laufend mit Daten vollpumpst, kannst du den Speicher-Verbrauch wohl kaum drosseln.
Zumindest solange du Java benutzt....
 

Neue Beiträge

Zurück