Speicherleck

Status
Nicht offen für weitere Antworten.

philthy

Mitglied
Guten Abend beisammen,

ich sitze schon den ganzen Nachmittag an einem kleineren Performanceproblem, mein Speicherverbrauch wächst nach paar Minuten Programmbenutzung auf 500 MB oder so an.

Ich kann den Fehler nichteinmal eingrenzen, nur mit jconsole schauen, wie sich der Verbrauch verändert.

Gibt es eine Möglichkeit, herauszufinden, wie viele Objekte einer bestimmten Klasse sich gerade im Programmspeicher tummeln oder so? Ich kann mir nicht vorstellen, dass 30 GeneralPaths mit jeweils 200 Punkten 400 MB Speicher brauchen.

Konstrukte wie

Code:
onMouseMove(Event e){
Point p = new Point(e.getX(), e.getY());
doSthWithP();
}

dürften es ja auch nicht ausmachen, oder? Beim nächsten Durchlauf refernenziert p ja ein anders Objekt und das alte kann gelöscht werden ...

Grüße
Philipp
 

philthy

Mitglied
Ja, hab das Problem jetzt einigermaßen "erkannt" dank JConsole und JVisualVM: Die Objekte werden schon dereferenziert wie gewünscht, nur sind es so viele (z.B. ein java.awt.Point pro Mausbewegung), dass sie einfach den Speicher schön zumüllen, bis der GC mal wieder vorbeikommt. Also alle 10 sek geht daws Maß wieder auf erträgliche 12 MB oder so herunter.

Danke für die Antworten!

Grüße
Philipp
 

Marco13

Top Contributor
"Speicher zumüllen" klingt, als wäre das etwas schlechtes. Wenn du einen Swimmingpool hast, würdest du den ja auch nicht nach jedem Schwimmen leer machen und vor dem nächste mal wieder füllen. Die VM verwendet so viel Speicher, wie sie verwenden darf. Warum sollte sie Sachen löschen, wenn sie keinen frischen Speicher braucht? Ein Speicherleck hat man grob gesagt, wenn der verwendete Speicher bei gegen unendlich gehender Verwendungszeit des Programmes auch gegen unendlich geht....
 

philthy

Mitglied
Aber ohne meinen "Eingriff" in Form von System.gc() geht der Speicher an die Grenze von 500 MB. System.gc() bringt das dann wieder auf 13 bis 20 MB. Dass System.gc() also so viele "Leichen" wegnimmt, sagt mir doch, dass irgendwetwas scheifläuft. Wären noch Referenzen da die ich beseitigen sollte, dann hätte der Garbage Collector da ja auch nichts ausrichten können.

Wollte ja gar nicht bestreiten, dass ich da meine Finger im Spiel habe ;) dachte nur, dass Objekte ohne Referenz schneller gelöscht werden.
 

FArt

Top Contributor
philthy hat gesagt.:
Aber ohne meinen "Eingriff" in Form von System.gc() geht der Speicher an die Grenze von 500 MB. System.gc() bringt das dann wieder auf 13 bis 20 MB. Dass System.gc() also so viele "Leichen" wegnimmt, sagt mir doch, dass irgendwetwas scheifläuft.
Das sollte es dir aber nicht sagen, weil es so nicht stimmt.

Der GC löscht nicht schnell sondern nach Bedarf. Ohne Bedarf u.U. kein Löschen. Sinnvoll, oder?
 

philthy

Mitglied
Ja, das stimmt schon. Ohne Bedarf kein Löschen. Klar. Ich belächle ja auch die Leute, die sich 4 GB Arbeitsspeicher reinbauen und dann alles tun, damit die tatsächliche Belegung 400 MB nicht überschreitet. Das resultiert dann nämlich in ewigen Wartezeiten und unnötigen Zugriffen auf die Festplatte.

Wenn mein Programm aber den ganzen Speicher ausnutzt (513 MB oder so) und dann mit einer OutOfMemory-Exception den korrekten Dienst verweigert, dann ist das ja eher schlecht. Da bestünde dann ganz konkreter Bedarf.

Ich sage ja nicht "das Programm nimmt 50 MB, das ist mir zu viel" sondern es ist einfach so, dass es nicht mehr funktioniert, wenn ich nicht durch bestimmte Aktionen System.gc() auslöse.

Ich gehe die ganze Zeit über den Code und überlege, an welcher Stelle mein schlechter Programmierstil was reißt. Es gibt aber keine Stelle, an der ich an einen Vektor mit einer Endlosschleife riesige Objekte anhänge :bahnhof:
 

FArt

Top Contributor
Einfach:
Bevor die VM einen OutOfMemory des Heaps meldet, wird versucht über den GC Speicher frei zu geben. Nur wenn das nicht möglich ist (weil zu viele Objekte noch referenziert werden) gibt es einen Fehler.
Dabei wird berücksichtigt, dass das collecten mehrstufig ist und nicht bei einem Lauf des GC durchgeführt wird.

So ungefähr glaube ich mich erinnern zu können, das in Dokus gelesen zu haben...
 

Wildcard

Top Contributor
Richtig, der GC braucht immer 2 Läufe um ein Objekt zu entfernen (wegen finalize), und es wird alles versucht um einen Out of Memory Error zu verhindern.
Bekommst du wirklich diesen Fehler, oder hast du nur die Befürchtung das es passieren kann?
 

SilentJ

Bekanntes Mitglied
Zusammen mit deinem Programmcode betrachtet, fällt mir nur folgende Analogie ein:

Du sitzt in deinem Auto, in einigen Hundert Metern befindet sich eine Mauer. Frohen Mutes und mit festem Griff umklammerst Du das Lenkrad und trittst voll aufs Gas. Ändert sich nichts an dieser Ausgangssituation, verhält sich das Auto vollkommen korrekt, wenn Du die Kraftentfaltung von einigen Kilonewton beim Aufprall auf die Mauer alsbald spürst.

Ansonsten, lade Dir die openJDK-Sourcen herunter, implementiere eine eigene Garbage Collection (viel Glück!) und werde glücklich.

Manche Probleme macht man sich selbst, dafür braucht man keine JVM.
 

philthy

Mitglied
Ich bekomme die Meldung, wenn ich einigermaßen kontinuierlich meine Pfade (ist ein Notizprogramm für Grafiktabletts) zeichne. Dann wird mit einem Minipfad 10 MB mehr Speicher belegt, mit dem nächsten dann vllt 15, und so weiter und so fort bis die 500 voll sind.

Der angehängte Screenshot zeigt den Verlauf. Das es so tief ist, liegt an meinem Aufruf von gc. Würde ich die ganze Zeit weiterschreiben, dann wäre die Kurve ziemlich viel höher.

memoryleak1.png
 

FArt

Top Contributor
Der Fehler liegt in doSmthWithP()... ;-)

Ne, ehrlich, sieht ja ganz nett aus, ist aber nicht hilfreich.

Poste minimalen Beispielcode, wie du die Pfade verwaltest, wie die VM gestartet wird usw.
 

philthy

Mitglied
SilentJ hat gesagt.:
Zusammen mit deinem Programmcode betrachtet, fällt mir nur folgende Analogie ein:

Du sitzt in deinem Auto, in einigen Hundert Metern befindet sich eine Mauer. Frohen Mutes und mit festem Griff umklammerst Du das Lenkrad und trittst voll aufs Gas. Ändert sich nichts an dieser Ausgangssituation, verhält sich das Auto vollkommen korrekt, wenn Du die Kraftentfaltung von einigen Kilonewton beim Aufprall auf die Mauer alsbald spürst.

Ansonsten, lade Dir die openJDK-Sourcen herunter, implementiere eine eigene Garbage Collection (viel Glück!) und werde glücklich..

Danke für deine Analogie und den Rest. Dazu folgende Fragen:

- an welcher Stelle behaupte ich, die GC sei schlecht implementiert? Ich bin mir schon im klaren darüber, dass der Fehler irgendwo in meinem Quelltext liegt. Die Zeiten "ich haue auf den Computer" waren bei mir noch nie so ausgeprägt. Außerdem solltest du am Screenshot erkennen, dass ich durchaus geduldig bin.

- Gib mir dann doch bitte einen Tipp, wie ich so etwas ("nimm die Mauskoordinaten und speichere sie") besser lösen kann.

Grüße
Philipp
 

philthy

Mitglied
SilentJ hat gesagt.:
Zusammen mit deinem Programmcode betrachtet, fällt mir nur folgende Analogie ein:

Du sitzt in deinem Auto, in einigen Hundert Metern befindet sich eine Mauer. Frohen Mutes und mit festem Griff umklammerst Du das Lenkrad und trittst voll aufs Gas. Ändert sich nichts an dieser Ausgangssituation, verhält sich das Auto vollkommen korrekt, wenn Du die Kraftentfaltung von einigen Kilonewton beim Aufprall auf die Mauer alsbald spürst.

Ansonsten, lade Dir die openJDK-Sourcen herunter, implementiere eine eigene Garbage Collection (viel Glück!) und werde glücklich..

Danke für deine Analogie und den Rest. Dazu folgende Fragen:

- an welcher Stelle behaupte ich, die GC sei schlecht implementiert? Ich bin mir schon im klaren darüber, dass der Fehler irgendwo in meinem Quelltext liegt. Die Zeiten "ich haue auf den Computer" waren bei mir noch nie so ausgeprägt. Außerdem solltest du am Screenshot erkennen, dass ich durchaus willig bin, solche Dinge zu durchdringen und nicht einfach bockig "alles scheiße" rufe.

- Gib mir dann doch bitte einen Tipp, wie ich so etwas ("nimm die Mauskoordinaten und speichere sie") besser lösen kann.

Grüße
Philipp
 

Landei

Top Contributor
Wenn es dir lieber ist, gegebenenfalls ein paar Points zu "verlieren" als dass deiner Anwendung die Puste ausgehen, kannst du die Points auch in SoftReferences verpacken.
 

philthy

Mitglied
Guten Morgen,

ich glaube, ich habe das Grundproblem begriffen:

Meine Punkte, die ich speichere, werden mit Bezierkurven verbunden, das heißt, ich verbinde immer z.B. Punkt 1 und 4 und nehme 2 und 3 als Bezierkontrollpunkte.

Nun muss ein "GeneralPath", den ich zeichne, ja immer seine tatsächlichen gnazzahligen Punkte ausrechnen, die gezeichnet werden sollen (so denk ich mir das) und DAS braucht dann eben den Speicher. Denn mit JVisualVM habe ich rausbekommen, dass 70% des Speichers mit Integern gefüllt sind und nur 1% oder so mit Datenstrukturen, die ich explizit mit new XY() erzeuge.

Wenn ich den GeneralPath nur aus Geradenstücken zusammensetze, d.h. curveTo() mit lineTo() ersetze, dann kommt der Speicher beim zügigen Schreiben mit dem Stift nur auf 40 bis 50 MB maximal und ist nach kurzer Zeit wieder frei, also 12 oder 13 MB.

Ach ja: Würde der GC nur "bei Bedarf" Speicher freigeben, so müsste er ja erst aktiv werden, wenn die eingestellte Obergrenze angekratzt wird. Wird dann wohl doch eine Mischung sein. Dafür spricht die Tatsache, dass sich die Speichernutzung automagisch alle paar Sekunden reduziert (nehme ich einfach mal naiv an).

Die Pfade zeichnen sich immer dann neu, wenn der Benutzer einen neuen Pfad beendet hat (weil ich den "aktuellen" Pfad in leicht abgespeckter Version zeichne, also ungeglättet und nur mit Linienstücken).

Ich werd mir also in der Richtung etwas effizienteres ausdenken müssen.

Vielen Dank für die Antworten und vor allem neuen Stichworte wie SoftReference und JVisualVM!

Grüße und einen schönen Tag euch,
Philipp
 

Ebenius

Top Contributor
philthy hat gesagt.:
[...] Denn mit JVisualVM habe ich rausbekommen, dass 70% des Speichers mit Integern gefüllt sind [...]

Ich kenne ja Deinen Code nicht, aber eventuell wäre noch ein Stichwort "boxing".

Ebenius
 

Wildcard

Top Contributor
Speicher dir doch die Punkte aus denen du den Path aufbaust, anstatt den Path selbst. Das eliminiert alle zusätzlich errechneten Punkte.

Ach ja: Würde der GC nur "bei Bedarf" Speicher freigeben, so müsste er ja erst aktiv werden, wenn die eingestellte Obergrenze angekratzt wird. Wird dann wohl doch eine Mischung sein. Dafür spricht die Tatsache, dass sich die Speichernutzung automagisch alle paar Sekunden reduziert (nehme ich einfach mal naiv an).
Der GC läuft auch wenn er Zeit und Lust hat. In GUI Applikationen z. B. gibt es häufig Phasen der Inaktivität die zum Aufräumen verwendet werden.
 

FArt

Top Contributor
philthy hat gesagt.:
Würde der GC nur "bei Bedarf" Speicher freigeben, so müsste er ja erst aktiv werden, wenn die eingestellte Obergrenze angekratzt wird. Wird dann wohl doch eine Mischung sein. Dafür spricht die Tatsache, dass sich die Speichernutzung automagisch alle paar Sekunden reduziert (nehme ich einfach mal naiv an).
Bedarf heißt nach eigenem Ermessen. Die GC der verschiedenen Generationen laufen auch nach unterschiedlichen Algorithmen. Bedarf ist auf jeden Fall auch gegeben, wenn Speicher benötigt wird aber vermeintlich keiner da ist.
 

philthy

Mitglied
Wildcard hat gesagt.:
Speicher dir doch die Punkte aus denen du den Path aufbaust, anstatt den Path selbst. Das eliminiert alle zusätzlich errechneten Punkte.

Ich denke, ein GeneralPath ist erstmal ein abstraktes Objet, das nur weiß, welche Punkte dazugehören und wie sie verbunden sind. Zum speichern der Information wird er also nicht viel Platz brauchen. Wenn er jetzt aber gerendert werden soll, dann müssen jawirklich alle Punkte berechnet werden. Beispiel Krei: Ein Kreis ist ja mit Mittelpunkt und Radius vollständig beschreiben. Will ich ihn aber Pixel für Pixel zeichnen, dann muss ich einen größeren Rechenaufwand betreiben. Ich weiß ja nicht, wie komplex die Algorithmen für Bezierkurven sind und wie viele Hilfsgrößen sich das System dabei generiert.

Wie man der Kurve entnehmen kann, schnellt der Bedatrf hoch und legt sich wieder, obwohl alle Pfade mit all ihren Informationen erhalten bleiben.

Hier ein Auszug aus der Methode, die die Mausbewegung aufzeichnet, also die Daten sammelt:

Code:
private void canvas1MouseDragged(java.awt.event.MouseEvent evt) {
		if (this.mode == MainUI.DRAWING) {
			thisPathsMovements ++;
			addPoint(new Point(evt.getX(), evt.getY()));
			Graphics2D g = (Graphics2D) ctrl.getOffscreenImage().getGraphics();
				g.setPaint(Color.black);
				// we draw the un-antialiased fine line which represents the mouse
				// movement and set this "prev point" to the current point
				g.setStroke(finestroke);
				g.drawLine((int) prevPoint.getX(), (int) prevPoint.getY(), evt
						.getX(), evt.getY());
				prevPoint.setLocation(evt.getX(), evt.getY());
			
			// .. and we append the point to the path we are creating.
			// Rules for that: every 2nd 3rd point is a bezier control point,
			// every 4th point is a point of the path.
			if (this.thisPathsMovements % 4 == 0) {
				try {					
					this.currentPath.curveTo(lastPoints[1].getX(), lastPoints[1].getY(), lastPoints[2].getX(), lastPoints[2].getY(), lastPoints[3].getX(), lastPoints[3].getY());
					//					this.currentPath.lineTo(lastPoints[3].getX(), lastPoints[3].getY());
					} catch (Exception e) {
					e.printStackTrace();
				}

			}
		
		}
		else if (this.mode == MainUI.SCROLLING) {
			if (this.timesRepainted % 2 == 0) {
				// wait a little bit for less powerful computers
				int xAbs = evt.getXOnScreen();
				int yAbs = evt.getYOnScreen();
				this.scrollPage(xAbs, yAbs);
				this.prevPoint.setLocation(xAbs, yAbs);
			}
		}
		updateCanvas();


Währenddessen ändert sich am Speicherbedarf fast nicht. Lasse ich nun die Maus (den Stift) aber los, wird die Zeichenfläche bereinigt und alle Pfade werden neu gezeichnet. Es kommt keine Information hinzu. Hier schnellt aber der Speicherbedarf in die Höhe. Er legt sich zwar wieder, aber nur, wenn ich dem Programm eine kleine Pause gönne. Schreibt man zügig weiter, werden keine "Altlasten" entfernt.

Ich werde das jetzt so machen, wie es mal war, nämlich "zweilagig" zeichnen: die untere Lage ist ein Image mit den bisherigen Strichen, auf einer oberen Lage wird die Mausbewegung mitgezeichnet. Wenn der Pfad fertig ist, wird er auf die untere Lage gezeichnet und die obere wird gelöscht. Dann muss ich das untere Bild nur aktualisieren, wenn an den "fertigen" Pfaden etwas geändert wurde.

Diese Idee hatte ich mal so implementiert aber verworfen weil ich dachte, dass es mehr Rechneleistung benötigt, immer ein transparentes Bild (= obere Lage) auf ein anderes zu legen.
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen

Ähnliche Java Themen

Neue Themen


Oben