OutOfMemoryError bei Bildverarbeitung

clupus

Grünschnabel
OutOfMemoryError bei Bildverarbeitung [SOLVED]

Hallo allerseits,

ich habe mich gerade mit Java an ein eues Programm gewagt und dor gleich einen Dämpfer erhalten:

Ich will ein einfaches Programm, dass mir alle Bilder in einem Verzeichnis als Thumbnail anzeigt, bei dem ich die Bilder anwählen kann und die angewählen Bilder dann verschieben (lassen) kann. Soweit hört sich das einfach an. Das Problem sind die Thumbnails:

Ich lade zunächst das reguläre Image als BufferedImage in Java, dann wird mittels getScaledInstance ein Thumbnail erzeugt. Das braucht schon mal recht viel Rechenzeit. Die Thumbnails (ca. 300x300) werden dann nicht gelöscht, sondern bleiben natürlich im Speicher, weil ich zum Zeichnen der Bilder auf JComponent-en die natürlich noch brauche. Wenn ich jetzt einige größere Dateien (halt mit der Spiegelrefelx gemacht) reinlade, kommt der OutOfMemoryError. Und zwar bei schon unter 30 Bildern/Thumbnails. Kann das sein?

Ich meine: Die unkomrimierten Bilder haben ca 2,5 MB. Da kämen bei 30 Bilder ca 64MB raus, wo ja auch die Grenze (standardmäßig) des Java-Heaps ist.

Also sehe ich 2 Möglichkeiten:
1. die Thumbails sind gar nicht umgerechnet sondern nur skaliert. Dann muss ich eine möglichkeit suchen, diese umzurechnen. Kann mir da jemand was zu sagen?
2. Ich muss meine GUI verbessern, so dass nur die aktuell angezeigten Bilder gerendert werden. Da weiß ich aber definitiv nicht, wo ich zu suchen anfangen soll. Ideen?

Vielen Dank
Christian
 
Zuletzt bearbeitet:
was die große heap-auslsatung angeht ...
du musst die bilder natürlich nach der verarbeitung auch wieder ENTLADEN ... also alle refferenzen auf diese schließen damit der garbage collector die resourcen wieder freigeben kann ...
wie genau nun die skalierung arbeit weis ich nicht ... müsste man mal ausprobieren in dem man ein 2500x1900 bild auf 100x100 zusammen shrinked und dann wieder auf 2500x1900 raufzieht ... wenn die qualität erhalten bleibt ist das definitiv ein problem für deine app ...
was mir dann einfallen würde wäre die skalierten bilder auf ein neues image zu zeichnen und skalierte instanz zu zerstören ... das sollte einiges an speicher wieder aufräumen ...

was das nicht-rendern des nicht-sichtbaren bereiches angeht kann ich dir leider nicht helfen da ich mich mit gui nicht so auskenne ...
 
Jetzt habe ich nur das Problem, dass ich keine Stelle finde, an der noch eine Instanz der Orginaldaten vorhanden ist, so dass der GC blockiert ist.

Ich hänge die Codes mal an. Interessant ist jimageorga.ImagePainter und die Innere Klasse.

Wenn jemand noch wass sieht, wo ich eine Referenz auf das BufferedImage habe, möge er mir das bitte sagen. Ansonsten wäre ich für weitere Hilfe dankbar.

Das einzige, was ich im Netz gefunden habe, war ein Verweis auf einen Bug, dass das BufferdImage nicht entladen werden könnte. Bevor ich aber daran glaube, will ich alles andere ausschließen.

Danke noch mal
Christian
 

Anhänge

noch deutlicher kann man RAM-EXPLOIT nich mehr dranschreiben ...
schuldige .. sollte eigentlich witzig gemeint sein ...

nein zum problem und mit ernst

du übergibst an PicReader ein verzeichnis ... was ja soweit noch ok is ... jedoch wird schon dieses file-objekt inner runtime gehalten und blockiert GC ... wird damit also nicht aufgeräumt
dann baust du n neuen imagepainter ... und zwar für jedes file n neuen ... das frisst ram und cpu ...
ich würde imagepainter so umschreiben das nur eine instanz notwendig ist
dann werden auch diese instanzen nicht aufgeräumt *p wird nicht null gesetzt*
weiter tiefer im code findet sich painter ... nach dessen aufruf img genulled wird ... nützblos nichts da painter die referenz hält da in diesem das file eben nicht genulled wird ... womit sich die kette bis main raufzieht und dir alles im ram kleben bleibt

wie gesagt ... noch deutlicher kann man es schon fast nicht mehr dran schreiben das dort eine OutOfMem-exception kommt ... vor allem bei großen daten ...

zur lösung deines problems würde ich nun wirklich noch mal dem logik-pfad folgen und kuggn wo man die entsprechenden instanzen nullen kann oder wo *grade bei klassen-instanzen* änderungen erfolgen können
grade das thema der objekt-gebundenen neu-instanzierung in einer schleife würde ich mir noch mal ganz genau überlegen ob es nicht auch mit EINER instanz und setter und getter methoden funktionieren würde ... da das alles ram und cpu frisst

hoffe ich hab dir jetzt mal n paar denkanstöße gegeben da ich den code nur grob überflogen habe und nur die von mir genannten problemstellen gefunden habe ...

ach btw : ich weis zwar nicht was unsere experten hier zu sagen ... aber ich würde immer
Java:
System.gc()
nutzen ... und nich
Java:
Runtime.getRuntime().gc()
... ich meine mal i-wo gelesen zu haben das es da unterschiede geben soll ... aber kann auch nur heiße luft sein =P
 
du übergibst an PicReader ein verzeichnis ... was ja soweit noch ok is ... jedoch wird schon dieses file-objekt inner runtime gehalten und blockiert GC ... wird damit also nicht aufgeräumt
dann baust du n neuen imagepainter ... und zwar für jedes file n neuen ... das frisst ram und cpu ...
ich würde imagepainter so umschreiben das nur eine instanz notwendig ist
dann werden auch diese instanzen nicht aufgeräumt *p wird nicht null gesetzt*
weiter tiefer im code findet sich painter ... nach dessen aufruf img genulled wird ... nützblos nichts da painter die referenz hält da in diesem das file eben nicht genulled wird ... womit sich die kette bis main raufzieht und dir alles im ram kleben bleibt

wie gesagt ... noch deutlicher kann man es schon fast nicht mehr dran schreiben das dort eine OutOfMem-exception kommt ... vor allem bei großen daten ...

OK, dann gehen wir das Ding mal durch:
MainFrame.PicReader ist ein Thread, damit meine AWT-Queue nicht blockiert wird, während die Daten geladen werden (ca. 1-3sec pro Bild skalieren!). Der bekommt eine Refernz auf EIN Verzeichnis. (Sobald der run()-Code abeglaufen ist wird auch die PicReader-Instanz und das File Objekt ge-null-t, da keine Instanzen mehr darauf verweisen)
Ich baue dann für jedes Bilchen einen eigenen ImagePainter. Das mache ich, damit ich das auch auf einer Swing-Oberfläche anzeigen lassen kann. Ich könnte mir als Alternative nur eine rießig große Fläche (JPanel) vorstellen, die dann alle Bilder in einem bestimmten Raster anzeigt und auf die ich dann noch "irgendwie" an bestimmten Stellen Texte und die gewünschte Checkbox einbauen/anzeigen muss. Der ImagePainer selber hat nur recht wenige Informationen gespeichert (eben Datei und eine Referenz auf den Painter, wecher auch ein JComponent ist). Damit erzeugt der ImagePainter auch noch nocht DIE Menge an Daten. (siehe dazu auch unten)
Im ImagePainter.Painter wird nur das Image anzeige (das Thumbnail) im RAM gehalten. Das sollte das kleine, skalierte Bildchen sein. Also auch kein Problem hier (s.u.). In setImage(BufferedImage image) wird kurzfristige das "große" Bild übergeben. Aber nur bei einem kleinen Bild wird das Image direkt verwendet, sonst wird skaliert (und damit theoretisch Datenmenge eingespart). Danach existiert keine Refernz mehr auf image, damit sollte der GC das große Bild zumindest wegschaffen können. Das Thumbnail soll er ja auch nicht löschen, da er hierfür ja rechnen muss und das deshalb im Thread im Voraus gemacht werden soll (beim Objekterzeugen), damit die GUI dann flüssig angezeigt werden kann.

Zum Speicherplatz. Ich rede hier nicht von hunderten oder tausenden von Objekten. Meine 64M Einstellung streikt bei 28 Bildern zu ca. 2,2 MB unskaliert. Ein Test mit 500 BufferedImages mit 300x300 Punken war kein Problem (Einfach ein Array füllen). Damit ist für mich eindeutig das Problem, dass Instanzen der unkomprimierten Bilder im RAM bleiben.
Dabei habe ich den Versuch gemacht, ein Bild auf 3x3 Pixel und dann wieder auf 300x300 zu ziehen zu dem Ergebnis gekommen, dass die Qualität "sichtlich gelitten hat" (große farbige Quadrate). Demnach ist das Skalieren mit getScaledInstance kein sklaieren bei dem das Orginal im Hintergrund bleibt sondern ein "echtes" Skalieren/Umrechnen. Und damit müssten, wenn er wirklich die 30 Thumbnails im RAM hat noch genügend Platz sein, wenn er die Bilder löschen würde.

Also bitte versuch mir noch mal klar zu machen, wie ich mit einer Instanz von ImagePainter auskommen soll (GUI beachten!). Ich seh' den Wald scheinbar vor lauter Bäumen nicht mehr.

Danke

Christian
 
was die klasse Painter angeht ... ich sehe nirgends ein
Java:
image=null;
vor dem gc ... womit die referenz auf image gehalten wird ...
was die sache mit der EINEN instanz von imagepainter angeht
den konstruktor erstmal leeren
dann drei methoden hinzufügen ... eine setter ... eine getter ... und eine methode zum nullen der referenzen ...
du könntest auch dein JPanel an imagepainter übergeben und in imagepainter dierekt darauf zugreifen was dir das add() in mainframe in die instanz verschieben würde womit sich die getter methode und die methode zum nullen erübrigen würde da alles nur noch in der setter methode ablaufen würde

das mit dem scaledinstanz wusste ich wie oben gesagt nicht .. aber gut das du es probiert hast ... jetzt wissen wir zumindest das deinem denkansantz nichts im wege steht

ich kann heute nachmittag mal versuchen da was zu implementieren da ich gleich los muss zur arbeit

hoffe du kannst dir halbwegs vorstellen was ich meine
 
Hi,

ein ganz einfacher Trick, um Deine Flatterobjekte zu finden, ist es einen HeapDump Deiner JVM zu schreiben, das Eclipse Memory Analyzer Toolkit zu holen und damit dann im Dominator Tree auf die Referenzierer Deines Speicherlecks zu schauen.
 
So ich habe jetzt mal einen Dump meines Heaps gemacht und mich da ein bisschen umgeschaut. Dazu habe ich zum Debuggen eine Refernz auf die Bilder abgeleg und dann den Heap durchsucht.

Ergebnis: Es gibt eine 2. Referenz (zu meiner Debug-Referenz) die über mehrere Objekte zu dem skalierten Bild führt:
Code:
BufferedImage <= sun.awt.image.OffScreenImageSource <= java.awt.image.FilteredImageSource <= sun.awt.image.ToolkitImage

Ich werde jetzt mal versuchen, das Bild in einer Kopie abzulegen. Mal sehen was dann passiert.

MfG
Christian
 
So,

ich hab's! Nach dem Löschen meinr Debug-Referenz auf das unkomprimierte Bild und dem Kopieren des skalierten Bildes in ein explizites BufferedImage ist der Heap-Verbrauch auch deutlich angenehmer geblieben. Insbesondere schießt er nicht mehr gegen unendlich, was echt nett ist ;-).

Danke noch mal an alle
Christian

@ SPiKEe:
Wenn du mir deine Verbesserungsvorschläge noch unterbreiten möchtest/würdest, wäre ich dir dankbar, dann kann ich ggf noch ein bisschen optimieren. Trotzdem Danke
 
na was code-optimierung angeht bin ich nich so ne kapazität ...
viele hier kritisieren eher meinen programmier stil *mag sein das er wirklich unsauber ist oder etwas umständlich zu lesen ... aber meist ist er doch ganz performant*

was die lösung angeht : es freut mich das du es doch geschafft hast ... das es allerdings mit einem expliziten umkopieren in eine eigene instanz machbar ist hätt ich noch nicht mal raten können da mein dann doch nicht so tief reicht

das einzige was ich dir noch anbieten könnte wäre eine variante mit nur einer instanz des picreaders ... viel mehr dann aber auch nicht mehr
 
Zurück