PDA

View Full Version : [Java] Anteprima immagini... problema memoria


+HookTheGhost+
11-12-2008, 22:17
Salve a tutti, stavo sviluppando un programma che mi permettesse di avere in un JPanel sulla sinistra le anteprime delle immagini contenute nella cartella di cui ho aperto il file immagine.

I problemi qui ora sono 2:

-Se le immagini sono di dimensioni abbastanza grandi 4000x3000 px circa (all'incirca la grandezza in cui salva le foto la mia macchina fotografica) dopo che ne carica qualche d'una java mi da un errore di memoria (heap space se non sbaglio)... ho semi risolto aumentando la memoria che la jvm può usare ma non mi sembra una risoluzione molto carina...

-In secondo luogo ho notato che quando viene richiamato il metodo per creare nuovamente le anteprima (è stata quindi cambiata cartella) gli elementi precedenti non vengono cancellati dalla memoria neanche con il comando System.gc() e quindi ben presto la memoria sale e mi da nuovamente l'errore precedentemente citato...

C'è qualche soluzione?

+HookTheGhost+
11-12-2008, 23:36
Ho fatto un piccolo programma d'esempio cosi potete provarlo e darmi qualche consiglio.
Vi dico subito che ho notato che anche se rimuovo i componenti e chiamo il metodo System.gc() le immagini restano in memoria infatti se carico un'altra cartella e poi decido di caricare di nuovo la prima la memoria non sale. Il problema è che non so come rimuoverli.
Premetto che nell'esempio l'ho fatto cosi al volo ma nel mio programma utilizzo i thread per non bloccare tutto il programma con il caricamento immagini, ma la situazione è identica tanto in questo caso mostro solo le anteprime...

Nel mio caso lo devo avviare con l'opzion -Xmx512m per avere un po' più di ram in modo che non mi si blocchi dopo 4 o 5 immagini per vie del problema per le grandi dimensioni che ho spiegato in precedenza

public class Main extends JFrame {
JPanel p = new JPanel();
JMenuBar mb = new JMenuBar();
JMenu m = new JMenu("File");
JMenuItem mi = new JMenuItem("Apri");
JMenuItem mm = new JMenuItem("Rimuovi");
public Main() {
aggiungiComponenti();
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser();
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if(fc.showOpenDialog(p) == JFileChooser.APPROVE_OPTION)
azione(fc.getSelectedFile());
}
});
mm.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
p.removeAll();
p.updateUI();
}
});

}
public void aggiungiComponenti() {
add(p);
mb.add(m);
m.add(mi);
m.add(mm);
setJMenuBar(mb);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setExtendedState(MAXIMIZED_BOTH);
setVisible(true);
}
public void azione(File dir) {
if(p.getComponentCount() != 0) {
p.removeAll();
p.updateUI();
}
File f[] = dir.listFiles();
ImageIcon icons[] = new ImageIcon[f.length];
for (int i = 0; i < f.length; i++) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
icons[i] = new ImageIcon(f[i].getAbsolutePath(), f[i].getName());
ImageIcon adactIcon = new ImageIcon(getScaledImage(icons[i].getImage(), 100, 100));
JButton thumbButton = new JButton(adactIcon);
thumbButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
p.add(thumbButton);
p.revalidate();
}
}
private Image getScaledImage(Image srcImg, int w, int h){
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, w, h, null);
g2.dispose();
return resizedImg;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main();
}
});
}

}

Aspetto consigli :D

+HookTheGhost+
12-12-2008, 23:59
UP :D Nessuno mi aiuta?

Dark Phoenix
13-12-2008, 11:46
Ecco fatto...

Ho aggiunto una riga nel main... tanto per promuovere il nuovo look&feel di java...

Serve almeno Java 6u10 per vederlo.... (è uscita la 6u11 da poco)
Comunque semplice il problema è che ti salvavi tutte le foto nell'array...

public class Main extends JFrame {
JPanel p = new JPanel();
JMenuBar mb = new JMenuBar();
JMenu m = new JMenu("File");
JMenuItem mi = new JMenuItem("Apri");
JMenuItem mm = new JMenuItem("Rimuovi");

public Main() {
aggiungiComponenti();
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser();
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (fc.showOpenDialog(p) == JFileChooser.APPROVE_OPTION)
azione(fc.getSelectedFile());
}
});
mm.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
p.removeAll();
p.updateUI();
}
});

}

public void aggiungiComponenti() {
add(p);
mb.add(m);
m.add(mi);
m.add(mm);
setJMenuBar(mb);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setExtendedState(MAXIMIZED_BOTH);
setVisible(true);
}

public void azione(File dir) {
if (p.getComponentCount() != 0) {
p.removeAll();
p.updateUI();
}
File f[] = dir.listFiles();
for (int i = 0; i < f.length; i++) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
ImageIcon adactIcon = new ImageIcon(getScaledImage(new ImageIcon(f[i].getAbsolutePath(), f[i].getName()).getImage(), 100, 100));
JButton thumbButton = new JButton(adactIcon);
thumbButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
p.add(thumbButton);
p.revalidate();
}
}

private Image getScaledImage(Image srcImg, int w, int h) {
BufferedImage resizedImg = new BufferedImage(w, h,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, w, h, null);
g2.dispose();
return resizedImg;
}

public static void main(String[] args) {
try {
UIManager.setLookAndFeel(new NimbusLookAndFeel());
} catch (UnsupportedLookAndFeelException e) {}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main();
}
});
}

}


comunque ti consiglio di usare il foreach molto che più pulito... ad esempio
public void azione(File dir) {
if (p.getComponentCount() != 0) {
p.removeAll();
p.updateUI();
}
File f[] = dir.listFiles();
for (int i = 0; i < f.length; i++) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
ImageIcon adactIcon = new ImageIcon(getScaledImage(new ImageIcon(f[i].getAbsolutePath(), f[i].getName()).getImage(), 100, 100));
JButton thumbButton = new JButton(adactIcon);
thumbButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
p.add(thumbButton);
p.revalidate();
}
}
diventa
public void azione(File dir) {
if (p.getComponentCount() != 0) {
p.removeAll();
p.updateUI();
}
for (File f : dir.listFiles()) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
ImageIcon adactIcon = new ImageIcon(getScaledImage(new ImageIcon(f.getAbsolutePath(), f.getName()).getImage(), 100, 100));
JButton thumbButton = new JButton(adactIcon);
thumbButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
p.add(thumbButton);
p.revalidate();
}
}

+HookTheGhost+
13-12-2008, 13:04
quale array? se intendi l'array di file ho provato a modificarlo come dici te ma la situazione non cambia

+HookTheGhost+
13-12-2008, 13:09
piuttosto vorrei dire che ieri sera mi sono accorto che usando la classe ImageIO si usa meno memoria e arrivo anche a liberarla ad ogni chiamata del metodo.

Però vorrei comunque sapere dove sbaglio in questa situazione perchè ci sarà qualcosa che dovrei fare ma non faccio dato che le immagini restano in memoria non so dove... grazie :)

Dark Phoenix
13-12-2008, 13:20
strano a me dava problemi ho modificato così e si è risolto....

evidentemente con una cartella con ancora più immagini il problema persiste...

ora provo a farne una con un centinaio di immagini.....


PS:
... mi funziona ho caricato ora molte immagini...
(l'array che causa problemi non è quello sui File... quella era solo una cosa che ti ho detto per fini estetici...)

fai copia incolla della classe che ho postato prima, e provala

+HookTheGhost+
13-12-2008, 13:27
Io sto facendo il test su 2 cartelle di circa 10 file ognuna di dimensioni intorno ai 3000x2000px:
carico la prima e arriva circa a 300 mb di ram
poi carico rimuovo
poi carico la seconda e va a circa 500
poi rimuovo e se ricarico la prima me la carica al volo senza che la memoria salga quindi probabilmente le immagini persistono anche se non so come

+HookTheGhost+
13-12-2008, 13:31
ma quella classe è uguale alla mia... l'ho provata e la memoria comqune non mi si azzera

+HookTheGhost+
13-12-2008, 13:35
ah ho capito! ok ma perchè toglieno la riga dove dichiari e inizializzi la prima icona cambia cosi tanto il programma?

Ho visto ora che hai sostituito questa:

ImageIcon icons = new ImageIcon(f[i].getAbsolutePath(), f[i].getName());
ImageIcon adactIcon = new ImageIcon(getScaledImage(icons.getImage(), 100, 100));


con questa


ImageIcon adactIcon = new ImageIcon(getScaledImage(new ImageIcon(f[i].getAbsolutePath(), f[i].getName()).getImage(), 100, 100));

Dark Phoenix
13-12-2008, 15:02
Per dirla semplice quello che fai è caricare in memoria l'immagine per poi fare il resize e visualizzarla...

Se mantieni l'immagine principale (non "resizata") in un array non consenti al garbage collector di rimuoverla, pertanto la memoria sale proporzionalmente alla dimensione delle immagini inserisci nell'array.

Al contrario se ne carichi una alla volta la converti e poi "butti" l'originale il garbage quando necessario provvederà a deallocare l'immagine originale.

PS.
La cosa importante che ho tolto è questo array qui nel quale salvavi le immagini complete
ImageIcon icons[] = new ImageIcon[f.length];

+HookTheGhost+
13-12-2008, 16:11
Ah si l'array di icone l'avevo creato per sbaglio in realtà nel mio programma non c'è infatti il problema nn è risolto...
Tento di spiegare cosa ho notato...
se non tocco il parametro della jvm -Xmx java usa 64 mb di ram per gestire le foto e fin qui ok, apro il primo set di foto e la ram va a 63, apro il secondo e noto che resta fissa a 63, riapro il primo set di foto e noto che dopo un po' di foto su una particolarmente pesante mi da l'errore Heap Space che indica che sono andato oltre la memoria richiesta...
ho provato allora ad avviarlo dandogli non più 64 mb di ram ma 128 è ho notato che caricando il primo set di voto vado salgo già a 127 e ripetendo il procedimento precedentemente descritto ho lo stesso problema :(

Ma com'è possibile che prima riusciva a caricarle usando i 64 mb e ora le stesse foto le carica usando tutti i 128? non può usare solo quelli necessari e accedere agli altri in caso di bisogno? Oppure il Garbage Collector viene chiamato quando si sta per sforare e non prima?

Dark Phoenix
13-12-2008, 18:12
Probabilmente il problema è da qualche altra parte, se utilizzi il main postato da me il problema c'è?

Il comportamento del Garbage Collector è imprevedibile e fondamentalmente lazy.
Più la memoria si sta esaurendo più diventa "aggressivo" il GC.

Generalmente il GC viene invocato dalla JVM quando si sta in condizioni critiche, tipicamente quando si è a corto di RAM, comunque il comportamento del GC è del tutto imprevedibile

Comunque se vuoi capire bene cosa occupa la tua RAM da java 6u7 se non sbaglio nella jdk è integrato JVisualVM un comodo tool visuale per spulciare i programmi java in esecuzione: puoi controllare thread, fare dump dell'heap e molte altre cose... lo trovi dentro la cartella bin della jdk ;)

+HookTheGhost+
13-12-2008, 18:50
si anche con il tuo prgoramma la cosa sussiste... :(
Se chiamo io con il comando System.gc() può cambiare qualcosa o non conviene fare operazioni di questo tipo?

Ma comunque quello che mi chiedo è a che scopo permettere di usare più memoria se poi viene usata "male"... nel mio caso se riesce a fare il 90 % delle operazioni con 64mb quando gliene do 128 dovrebbe farcela alla grande invece ripete le precedenti prestazioni, mi sembra un po' assurdo...

Proverò comunque anche quei tool che mi hai consigliato ;)

Spero di riuscire a risolvere prima o poi

+HookTheGhost+
13-12-2008, 19:35
mi correggo il tuo va il problema c'è quando metto il metodo azione in un Thread la cosa dopo un po' salta probabilmente sbaglio io... anzi di fisso :D


mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser();
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (fc.showOpenDialog(p) == JFileChooser.APPROVE_OPTION) {
final File fl = fc.getSelectedFile();
Thread t = new Thread(new Runnable() {
public void run() {
azione(fl);
}
});
t.start();
}
}
});


dove sbaglio?

Dark Phoenix
13-12-2008, 19:41
si anche con il tuo prgoramma la cosa sussiste... :(
Se chiamo io con il comando System.gc() può cambiare qualcosa o non conviene fare operazioni di questo tipo?

si può fare ma è molto brutta come cosa (sporca il codice, non garantisce il funzionamento e probabilmente il problema è altrove)...

guarda ho provato a fare così quel metodo...

public void azione(File dir) {
if (p.getComponentCount() != 0) {
p.removeAll();
p.updateUI();
}
for (File f : dir.listFiles()) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
ImageIcon adactIcon = new ImageIcon(f.getAbsolutePath(), f.getName());
adactIcon = new ImageIcon(adactIcon.getImage().getScaledInstance(100, 100, Image.SCALE_DEFAULT));
JButton thumbButton = new JButton(adactIcon);
thumbButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
p.add(thumbButton);
p.revalidate();
}
}

sulle mie poche foto ora consuma ancora meno RAM...
prova tu su qualcosa di più grande magari ottieni comunque benefici.

Scusa se vado a tentativi ma non ho foto grandi e conosco poco java awt e swing :)

+HookTheGhost+
13-12-2008, 19:47
il tuo programma funziona si scusa il problema lo ho quando uso i Thread guarda sopra... mi sa che sbaglio qualcosa nel loro utilizzo (anche se un programma demo di Sun faceva tale e quale)

Dark Phoenix
13-12-2008, 20:03
il tuo programma funziona si scusa il problema lo ho quando uso i Thread guarda sopra... mi sa che sbaglio qualcosa nel loro utilizzo (anche se un programma demo di Sun faceva tale e quale)

Strana cosa.... se lo lancio anche col thread gira uguale...

questa volta non riesco a riprodurre quel che ti accade...

+HookTheGhost+
14-12-2008, 09:05
Io ho provato con 2 cartelle una da 20 e l'altra da 130 foto la prima la carica no problem e dopo invece nel caricare la seconda da l'errore... le dimensioni sono sempre le stesse... :mad: Uffy ma non è giusto

Dark Phoenix
14-12-2008, 13:07
Perdonami se ti posto tutto il codice, ma è per stare sicuri... non ho foto grandi prova tu con questo...


public class Main extends JFrame {
JPanel p = new JPanel();
JMenuBar mb = new JMenuBar();
JMenu m = new JMenu("File");
JMenuItem mi = new JMenuItem("Apri");
JMenuItem mm = new JMenuItem("Rimuovi");

public Main() {
aggiungiComponenti();
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser();
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (fc.showOpenDialog(p) == JFileChooser.APPROVE_OPTION) {
final File fl = fc.getSelectedFile();
Thread t = new Thread(new Runnable() {
public void run() {
azione(fl);
}
});
t.setDaemon(true);
t.start();
}
}
});
// mi.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// JFileChooser fc = new JFileChooser();
// fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
// if (fc.showOpenDialog(p) == JFileChooser.APPROVE_OPTION)
// azione(fc.getSelectedFile());
// }
// });
mm.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
p.removeAll();
p.updateUI();
}
});

}

public void aggiungiComponenti() {
add(p);
mb.add(m);
m.add(mi);
m.add(mm);
setJMenuBar(mb);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setExtendedState(MAXIMIZED_BOTH);
setVisible(true);
}

public void azione(File dir) {
if (p.getComponentCount() != 0) {
p.removeAll();
p.updateUI();
}
for (File f : dir.listFiles()) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
ImageIcon adactIcon = getImage(f);
JButton thumbButton = new JButton(adactIcon);
thumbButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
ImageObserver imageObserver = adactIcon.getImageObserver();
p.add(thumbButton);
p.revalidate();
}
}

private void loadImage(File f) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);
ImageIcon adactIcon = getImage(f);
JButton thumbButton = new JButton(adactIcon);
thumbButton.setAlignmentX(JButton.CENTER_ALIGNMENT);
ImageObserver imageObserver = adactIcon.getImageObserver();
p.add(thumbButton);
p.revalidate();
}

private ImageIcon getImage(File f) {
Image i = new ImageIcon(f.getAbsolutePath(), f.getName()).getImage();
Image scaledInstance = i.getScaledInstance(100, 100, Image.SCALE_DEFAULT);
i.flush();
return new ImageIcon(scaledInstance);
}

public static void main(String[] args) {
try {
UIManager.setLookAndFeel(new NimbusLookAndFeel());
} catch (UnsupportedLookAndFeelException e) {}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main();
}
});
}

}

In caso se questa è la soluzione al tuo problema la discutiamo...

+HookTheGhost+
14-12-2008, 17:14
Fenomenale!! Sembra funzionare anche con 260 immagini molto grandi (circa 230 mbin totale)!! GOOD!!

Ora mi devi spiegare un paio di cose:

la riga nel metodo azione

ImageObserver imageObserver = adactIcon.getImageObserver();


la seconda è la funzione del metodo flush per l'immagine

ah anche setDeaemon(true) a cosa serve di preciso?

Dark Phoenix
14-12-2008, 18:04
perdonami la riga
ImageObserver imageObserver = adactIcon.getImageObserver();
è un refuso puoi anche cancellarla... (ho esplorato un po le librerie e mi è avanzato il metodo :p )

Ora tornando alle cose serie...
Con il setDaemon sul thread indichi che il Thread è da considerarsi per l'appunto un demone, la jvm non termina finchè c'è almeno un Thread non demone attivo.
Il Thread principale, il "main", è un thread non demone.
Per maggiori dettagli guarda qui (http://oreilly.com/catalog/expjava/excerpt/index.html) o nella javadoc.

Il metodo flush (http://java.sun.com/javase/6/docs/api/java/awt/Image.html#flush()) come scritto nella javadoc in pratica resetta l'immagine al suo stato iniziale, come se avessi appena creato l'oggetto.
Quel che accade è che caricando le immagini con Image tale classe effettua un caching dell'immagine caricata così da velocizzarne il riutilizzo (consumando memoria però).

Entrambe le operazioni (flush e daemon) sono volte a migliorare il comportamento del garbage collector, con daemon segnali un thread demone, con flush svuoti i buffer all'interno di Image ;)

+HookTheGhost+
14-12-2008, 18:14
Ottimo grazie mille i test procedono positivamente in caso contrario in futuro mi farò di nuovo vivo :D

Grazie ancora dell'aiuto ;)

Dark Phoenix
14-12-2008, 18:22
Di nulla è un piacere ;)

...ultimamente mi sto interessando del "problema prestazioni"
...in caso mi trovi qui :cool:

+HookTheGhost+
17-12-2008, 12:28
c'è un'altra cosa che non so... nel caso in cui avvio una nuova procedura come faccio a bloccare quella precedente? Altrimenti ho notato che viaggiano in contemporanea ed è un casino

Dark Phoenix
31-12-2008, 12:38
Per interrompere un Thread c'è il metodo Thread.stop() ma è deprecato.
La soluzione corretta ed elegante è la seguente:
per terminare il Thread si utilizza il metodo Thread.interrupt(), il Thread poi provedderà autonomamente a terminarsi da solo, controllando il valore di Thread.isInterrupted().

Nel tuo caso ad esempio per ogni foto controlli il valore isInterrupted(): se è false allora vai avanti altrimenti esci.

Buon Anno! :)