# ImageIO.read verursacht heap space



## ulr!ch (13. Dez 2007)

Hi JavaGemeinde,

ich habe nun die letzten drei Tage damit zugebracht, alle möglichen Foren, APIs, Codebsp. ... nach einer Lösung durchzuforsten, deswegen schickt mich bitte nicht weg.   
Also, folgendes Problem, ich habe einen JTabbedPane, auf dem bis zu 25 Panels als KarteiKarten angezeigt werden. Auf den Panels sind JLabel, die über new ImageIcon() ein BufferedImage darstellen. Dieses BufferedImage besteht aus einer png-Datei (ca. 750 x 350 px), auf die zusätzlich noch ein paar Striche gezeichnet werden müssen.
Wenn ich nun das erste Mal die 25 Panels mit den 25 Labels anzeigen lasse, funktioniert alles einwandfrei, auch beim zweiten Mal, aber beim Dritten mal sind die 64MB heap space aufgebraucht, und dass obwohl ich
a) das JTabbedPane lösche,
b) das Panel lösche,
c) flush() und System.gc() ein paar Mal drin stehen habe...

Ich hab' echt alles ausprobiert. Hier mal ein wenig Code:

```
importe [...]

public class profil extends JFrame {
  private static BufferedImage bimage = new BufferedImage(750, 350, BufferedImage.TYPE_INT_RGB);

  public BufferedImage getBufferedImage([...]) {
    try {
// Bimage mit ImageIO aus einer png-Datei laden
        try {
        	bimage = ImageIO.read(new File("X:/[...]/abc.png"));  // wird das 52 Mal aufgerufen, hängt's
        }
        catch (IOException e) {
           e.printStackTrace();
        } 
    }
    catch (NullPointerException npe) {
      System.out.println(npe);
    }

// Linien zeichnen
    Graphics g = bimage.getGraphics();
    Graphics2D linie = (Graphics2D) g;
//    [blabla]
    
    repaint();
    return(bimage);
  }
}
```

Kann jemand helfen?

Liebe Grüße,
Ulr!ch


----------



## Wildcard (13. Dez 2007)

Warum 25 mal das gleiche Bild laden?


----------



## ulr!ch (13. Dez 2007)

Wildcard hat gesagt.:
			
		

> Warum 25 mal das gleiche Bild laden?


Es sind drei verschiedene, auf die dann gezeichnet wird.
Wie kann ich das denn optimieren, AFAIK gibt's bei BufferedImage kein clone().

LG,
Ulr!ch


----------



## Wildcard (13. Dez 2007)

Wenn du 3 Bilder hast solltest du 3 Bilder laden und nicht 25.


----------



## ulr!ch (13. Dez 2007)

Wildcard hat gesagt.:
			
		

> Wenn du 3 Bilder hast solltest du 3 Bilder laden und nicht 25.


Hi Wildcard,

ich verstehe immer noch nicht, wie das mein Problem löst.  :? 
Wenn ich Bild A habe, dass dann die Grundlage für die Bilder 1, 14, 15, 16 und 20 ist, dann muss ich Bild A ja irgendwie vervielfältigen. Und dann habe ich dann hinter doch wieder 6 Referenzen (eine auf Bild A, Bild A(1) usw.).

Es geht doch um die Anzahl der Referenzen.
Kannst du vielleicht ein wenig genauer beschreiben, was du meinst?

Danke im Voraus,
Ulr!ch


----------



## Wildcard (13. Dez 2007)

Warum vervielfältigen? Du kannst das gleiche Bild immer wieder verwenden.
Schreib dir eine ImageRegistry dafür, die die Bilder in einen internen Cache lädt:

```
public class ImageRegistry {
	private static HashMap<String, BufferedImage> registy = new HashMap<String, BufferedImage>();
	
	public static BufferedImage getImage(String path)
	{
		BufferedImage image = registy.get(path);
		if(image!=null)
			return image;
		return loadImage(path);
	}

	private static BufferedImage loadImage(String path) {
		try {
			return ImageIO.read(new File(path));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}
```


----------



## ulr!ch (13. Dez 2007)

Hi Wildcard,

danke für deine Antwort. Ich befürchte, ich bin zu blöd...   
Ich muss doch irgendwie die HashMap füllen und das mache ich doch mit registy.put("abc", bufferedImage);

Und später rufe ich dann doch die externe Klasse mit:

```
Graphics g = ImageRegistry.getImage("X:/[...]/1.png").getGraphics();
    Graphics2D linie = (Graphics2D) g;
```
auf, oder?

Sorry, dass ich jetzt auf den Keks gehe, aber meine Nerven sind echt am Ende...
Wie kann ich also die HashMap füllen?

Liebe Grüße,
Ulr!ch


----------



## Wildcard (13. Dez 2007)

Ach ja, das put hatte ich vergessen, das solltest du in load einbauen, sonst bringt das ganze nicht viel  :lol:


----------



## ulr!ch (14. Dez 2007)

Wildcard hat gesagt.:
			
		

> Ach ja, das put hatte ich vergessen, das solltest du in load einbauen, sonst bringt das ganze nicht viel  :lol:


Hi Wildcard,

dann sieht die Klasse nun so aus:

```
public class ImageRegistry {
	   private static HashMap<String, BufferedImage> registy = new HashMap<String, BufferedImage>();
	   private static BufferedImage bi01 = new BufferedImage(750, 350, BufferedImage.TYPE_INT_RGB);

	   public static BufferedImage getImage(String path)
	   {
	      BufferedImage image = registy.get(path);
	      if(image!=null)
	         return image;
	      return loadImage(path);
	   }

	   private static BufferedImage loadImage(String path) {
	      try {
	   	   registy.put(path, bi01);
	    	  return ImageIO.read(new File(path));
	      } catch (IOException e) {
	         // TODO Auto-generated catch block
	         e.printStackTrace();
	      }
	      return null;
	   }
	}
```

Und um auf dieses BufferedImage zeichnen zu können, rufe ich folgendes auf:

```
bimage =ImageRegistry.getImage("X:/[...]/abc.png");
    Graphics g = bimage.getGraphics();
    Graphics2D linie = (Graphics2D) g;
```

Wenn ich die Klasse, in der der zweite Codeabschnitt steht, mehrmals aufrufe, dann schreibt er mir:
a) ab der zweiten Karteikarte alle Striche übereinander,
b) lädt er das Bild nicht mehr und
c) verstehe ich nicht zum zeichnen rufe ich ImageRegsitry.getImage(String path) auf, die dann wiederum loadImage(String path) aufruft. Hier wied jedesmal ImageIO.read(new File(path)) aufgerufen. Wo liegt hier die Vereinfachung.   

Sorry, ich versteh's nicht. Vielleicht kannst du das noch mal drüberschauen; wo liegt mein logischer Fehler?

Alles Gute,
Ulr!ch


----------



## Wildcard (14. Dez 2007)

Ist denn jedes Bild anders bemalt? Das hast du vorher zB nicht erwähnt.
Wenn es der Fall ist, zeichne lieber in der Component die das Bild enthält die Striche oben drauf, als zig Bilder in den Speicher zu klopfen und dich dann zu wundern wenn er irgendwann ausgeht.


----------



## ulr!ch (14. Dez 2007)

Wildcard hat gesagt.:
			
		

> Ist denn jedes Bild anders bemalt? Das hast du vorher zB nicht erwähnt.
> Wenn es der Fall ist, zeichne lieber in der Component die das Bild enthält die Striche oben drauf, als zig Bilder in den Speicher zu klopfen und dich dann zu wundern wenn er irgendwann ausgeht.


Ja, jedes Bild ist anders bemalt. Gott sei Dank, ich dachte schon, ich hätte was grundlegendes verpeilt.    

Die Komponente, auf die dann gezeichnet würde, wäre ein JLabel. Wenn ich dich richtig verstehe, dann setIcon(new ImageIcon())? Dann kann ich mit Graphics g darauf zeichnen, ohne dass ich das Bild darunter "lösche"?

Wenn du mir da noch ein wenig Info geben kannst.

Liebe Grüße,
Ulr!ch


----------



## Wildcard (14. Dez 2007)

Überschreib paintComponent, ruf als erstes super.paintComponent auf und danach kannst du Linien zeichnen.


----------



## ulr!ch (15. Dez 2007)

Ok, Wildcard, das war ein echt großer Schritt in die richtige Richtung.  :applaus: 
Jetzt habe ich nur noch ein kleines Problem, und zwar folgendes:


```
public class profil extends JLabel {
  int gesamt;
  Double[] zahlenX;
  
  public profil(int gesamt, Double[] zahlenX) {
	  this.gesamt = gesamt;
	  this.zahlenX = zahlenX;

// 1. Aufruf
	  System.out.println("--> "+gesamt);
	  System.out.println("--> "+zahlenX[1]+"; "+zahlenX[2]);

          setIcon(new ImageIcon("X:/[...]/abc.png"));
          repaint();
  }

  public void paintComponent(Graphics g) {
// 2. Aufruf
  	System.out.println("-->2 "+gesamtScore);
  	System.out.println("-->2 "+zahlenX[1]+"; "+zahlenX[2]);

  	super.paintComponent(g);
// Linie - int
	  Graphics2D linie = (Graphics2D) g;
    	  linie.drawLine((int)(250+50*(new Double(gesamt))), 50,
	    		       (int)(250+50*(new Double(gesamt))), 50);
// Linie - Double[]
  	  linie.drawLine(new Double(xKoordinaten[i]+50*zahlenX[i].doubleValue()).intValue(), yKoordinaten[i],
	  		       	   new Double(xKoordinaten[i+1]+50*zahlenX[i+1].doubleValue()).intValue(), 
  }
}
```

Und bei der ersten Consolenausgabe werden int und Double[] richtig angezeigt, beim zweiten Aufruf werden int und Double[] vom letzten Aufruf angezeigt. Nun kommt aber das verrückte. Die Linie - int wird korrekt gezeichnet, also variable, wohingegen die Linie - Double[] immer dieselbe ist, obwohl die übergebenen Werte unterschiedlich sind.
Ich versteh' die Welt nicht mehr... 
 :shock: Wie kann denn das sein?  ???:L 
Hast du vielleicht darauf eine Antwort bzw. einen Ansatz zum Lösen.

Liebe Grüße,
Ulr!ch


----------



## ulr!ch (15. Dez 2007)

Hi,

ich bin's noch einmal. Das spuckt die Konsole aus.
Ich verstehe nicht - und vielleicht liegt da das Problem - weshalb der repaint-Befehl also der Aufruf von paintComponent so spät erfolgt...


```
--> 35
--> 300; 508
--> 33
--> 440; 422
--> 24
--> 343; 344
-->2 35
-->2 343; 344
-->2 35
-->2 343; 344
```

Seltsam...
Liebe Grüße,
Ulr!ch


----------



## trazzag (15. Dez 2007)

wo kommen denn die Arrays xKoordinaten und yKoordinaten her? Und wo / wie dein i definiert ist, sehe ich auch irgendwie noch nicht.


----------



## ulr!ch (15. Dez 2007)

trazzag hat gesagt.:
			
		

> wo kommen denn die Arrays xKoordinaten und yKoordinaten her? Und wo / wie dein i definiert ist, sehe ich auch irgendwie noch nicht.


Die Arrays kommen von der aufrufenden Klassen, das zumindest könnte man sehen.
Das i ist ein Laufparameter einer for-Schleife, die ich rausgenommen habe, um den code übersichtlicher zu machen.

Das Problem ist, die Arrays werden zwar korrekt übergeben, zu dem Zeitpunkt, an dem die paintComponent jedoch aufgerufen wird, hat der letzte Eintrag alles andere überschrieben. Ich fände es logisch, wenn es bei dem int genauso wäre, ist es aber nicht...

Liebe Grüße,
Ulr!ch


----------

