Salut,
je crée des BufferedImage en utilisant la fonction createCompatibleImage de GraphicsConfiguration, mais le flush() de BufferedImage ne libère pas la mémoire utilisée ?
Comment ça se fait ?
Que dois-je faire pour y remédier ?
Version imprimable
Salut,
je crée des BufferedImage en utilisant la fonction createCompatibleImage de GraphicsConfiguration, mais le flush() de BufferedImage ne libère pas la mémoire utilisée ?
Comment ça se fait ?
Que dois-je faire pour y remédier ?
Le flush n'agit pas sur les pixels contenus dans le BufferedImage.
Pour libérer la mémoire, il faut affecter null à l'instance.
C'est étrange, j'ai essayé null et flush, ça ne libère pas la mémoire.
Par contre les BufferedImage crées par le chargement par les méthodes ImageIO.read de javax.imageio libèrent bien leur mémoire avec un flush sans besoin d'annuler le pointeur.
Je suppose que la méthode pour libérer la mémoire d'une BufferedImage existe mais je n'arrive pas à la trouver. J'ai fouillé dans la classe Raster sans meilleur résultat.
Je fais un jeu vidéo qui demande une bonne gestion mémoire pour bien fonctionner et le fait de ne pas pouvoir libérer la mémoire de ces types d'images est un sérieux handicap.
Pour l'instant l'optimisation que j'ai fait consiste à en créer le moins possible et à les réutiliser. Mais ça fait au minimum un buffer par taille d'image de travail et la mémoire se remplit petit à petit sans moyen de la vider.
Là j'ai vraiment besoin de libérer cette mémoire et je ne peux pas croire que les gars de Sun n'aient jamais été confrontés au problème.
Vivement une solution.
Il n'y a pas de méthodes explicites puisqu'a priori, la libération est automatique.
J'ai essayé, et a priori je n'ai pas de problèmes de libération. Je suis en java 6.
Tant que la JVM n'a pas besoin de plus de mémoire, il n'y a que très peu de chance pour que le garbage collector soit lancé. Contente-toi de supprimer les instances des BufferedImage que tu n'utilises plus, et la JVM fera le reste.
Au besoin, fait quelques tests avec un System.gc() pour voir si ça change quelque chose.
Pense aussi à libérer du temps pour ta JVM. Je pense par exemple à un thread sans pause très consommateur (CPU, accès disque) : la JVM mettra un peu plus de temps à se décider à libérer la mémoire.
Merci pour vos conseils mais ca ne me fonctionne toujours pas.
J'utilise System.runFinalization et System.gc pour obliger le Garbage Collector à fonctionner, et j'ai intégré le processus dans une boucle qui permet de savoir si l'action du Garbage Collector est terminée ou non.
Je travaille en monotache. C'est un jeu vidéo que je fais, je dois contrôler parfaitement les ressources et en particulier la mémoire. Pour ce qui est du processeur, je me débrouille très bien.
Je suis moi aussi en java 1.6 et la libération de mémoire ne se fait pas dans le cas suivant :
- Création de l'image par GraphicsConfiguration.createCompatibleImage()
- Libération suposée par flush() et annulation du pointeur
Au secours ! J'ai besoin de cette libération de mémoire, de mon côté je vous tiens au courant si j'ai du nouveau et je continue à fouiller.
Pourquoi vouloir absolument libérer la mémoire alouée à la JVM ? Si tu crés des objets, la JVM va alors libérer de la mémoire pour la données aux nouveaux objets créés.
Après, il faut voir la mémoire max allouée à la JVM au démarrage.
Tu veux libérer la mémoire à cause d'un autre processus qui tourne en parallèle de la JVM ?
Lorsque la jvm essaie d'allouer un objet, qu'il n'y arrive pas, il fait l'équivalent d'un gc, et recommence.
Si vraiment il n'y arrive pas, alors il remonte une OutOfMemoryException.
Quelque soit la source de l'erreur, c'est une fausse piste d'appeler System.gc(), ou System.runFinalization().
Je pense également que flush() est une fausse piste.
J'ai bien créé les images avec GraphicsConfiguratin.createCompatibleImage(), comme tu le suggerais.
Je me souviens avoir participé à un post sur un sujet similaire. Je regarde ce qu'il en est sortie, et je te tiens au courant.
Est ce qu'il est possible que tu aies gardé une référence sur un Graphics, créé à partir de l'image à liberer?
Comme je l'ai déjà dit, pas de processus parallèle et j'alloue 384 Mo à la JVM, je travaille sur une petite configuration. Pour te convaincre de mes besoins et compétences, je te conseille d'aller voir le jeu sur mon site et de tester la démo. Si tu me donnes les résultats de tes tests ou des conseils sur ce que tu saurais faire à ma place, cela me fera grand plaisir.Citation:
Envoyé par dinobogan
Je viens de vérifier, il n'y en a pas, mais je n'avais pas pensé à ça pour la libération de mémoire.Citation:
Envoyé par Sanguko
J'ai regardé aussi du côté de la fonction getSource() de BufferedImage, ça ne donne pas de meilleur résultat.
Pour la BufferedImage créée par GraphicsConfiguration c'est effectivement le cas, flush() ne libère que des données d'optimisation et libère, je suppose, les sources données par la fonction getSource().Citation:
Envoyé par Sanguko
Cependant flush() fonctionne sur les Image et sur des BufferedImage créées par ImageIO.read() et disposant de source(s).
Citation:
Envoyé par dinobogan
Pour le jeu que je fais, j'ai vraiment besoin de libérer toute la mémoire à un instant donné, pour les chargements de maps en particulier.Citation:
Envoyé par Sanguko
Je travaille en monotâche et je veux vraiment contrôler les ressources à n'importe quel instant. Une map occupe en mémoire entre 20 et 250 Mo, je dois savoir de combien de mémoire je dispose. Je ne programme pas PONG, même si je ne programme pas non plus WOW7.
La difficulté potentielle avec Java est une gestion correcte et précise de la mémoire. Le garbage collector s'en sort très bien et je l'appelle volontairement pour gagner du temps, je ne le fais pas à chaque tour de boucle.
Le problème que j'ai est vraiment spécifique au fait que les BufferedImage créées par GraphicsConfiguration.createCompatibleImage() ne libèrent pas leur mémoire par les moyens habituels. (pointeur = null, flush, etc ...)
Volontiers.Citation:
Envoyé par Sangoku
Effectivement le problème ne date pas d'hier et j'y avais été confronté il a 6 ans, mais je pensais qu'une solution avait été apportée. C'est très récemment que j'ai vu que la spécification du flush() de BufferedImage est plus pauvre que celle du flush() de Image. Connaissant le travail de Sun, je cherche la raison de cet appauvrissement fonctionnel ou une solution permettant de retrouver la spécification originale de flush(), c'est à dire une libération complète des ressources utilisées.
Si une methode flush sur Image existe, c'est parce que certaines images manipulent des ressources critiques qui ne sont pas connus du GC.
Par exemple, la VolatileImage tape dans la mémoire spécifique de la carte graphique. Ce qui veut dire que même si une VolatileImage n'est plus référencée, elle peut empecher la création d'une nouvelle VolatileImage (tant que les finalizer n'ont pas été executé en fait).
Bref je pense que tu as raison d'appeler flush pour libérer les ressources que le gc ne maitrise pas, mais que ce n'est pas la source de ton OutOfMemoryException.
Par ailleurs, je pense que c'est toujours un peu compliqué de se fier à System.gc(). A priori, le fait d'appeler gc ne te permet d'avoir plus de mémoire disponible. Si tu n'as pas de OutOfMemoryException avec gc, tu n'en a pas sans. Simplement, tu as plus de chance que le gc ne se produise pas à un moment critique (ce qui est peut être ce que tu veux dans un jeu).
J'ai retrouvé le thread dont je parlais. Le problème était un référence qui avait été conservée sur les BufferedImage. C'est un peu délicat, parce que on ne sait pas vraiment quels sont les objets de la vm qui peuvent garder une référence sur une BufferedImage. Graphics était une possibilité. Il faudrait un outil pour connaitre toutes les references qui pointent vers un objet donné, mais je n'en connais pas.
Sans outil, il faut essayer de trouver peut être en partant d'un test simple, et en ajoutant les spécificités de ton jeu sur l'utilisation des BufferedImage.
C'est pauvre comme réponse, mais pour le moment, je ne vois pas d'autre piste.
Tu avais raison, Sangoku, pour le graphics.
En fait il occupe un bonne quantité de ressources.
Il faut appeler volontairement la méthose finalize() pour libérer la mémoire qu'il occupe.
Pour un flush adapté à BufferedImage ça nous donne :
Utilisé comme ça :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 public static BufferedImage flushBufferedImage(BufferedImage buffer) { if (buffer == null) return null; buffer.flush(); flushGraphics(buffer.getGraphics()); return null; } public static Graphics flushGraphics(Graphics graphics) { if (graphics == null) return null; try { graphics.finalize(); } catch (Exception e) { e.printStackTrace(); } return null; }
Merci à vous tous.Code:
1
2
3
4 BufferedImage buffer = createBuffer(); // Création du buffer // Travaux graphiques buffer = flushBufferedImage(buffer); // Libération mémoire et déréférencement
Le code sur Graphics me parait un peu surréaliste.
getGraphics() crée une nouvelle instance de Graphics. A chaque appel. De plus finalize ne doit pas être appelé par un programme, c'est une fonction plutot reservée au gc. Tu peux par contre utiliser dispose(). Mais bon, encore une fois, je vois pas l'intérêt de construire un Graphics pour le détruire apres.
Je suis d'accord, Sanguko, sur le côté surréaliste de la chose mais deux indices m'ont mis la puce à l'oreille :
- tu as évoqué le fait d'aller y fouiller alors que de mon côté je ne l'envisageais pas du tout
- la méthode finalize() de Graphics est particulièrement bien documentée alors que c'est une fonction standard non documentée pour la plupart des objets.
J'ai donc essayé, et il se trouve que ça fonctionne effectivement mieux. Il semble que createGraphics() alloue un espace supplémentaire dans Graphics, espace pas forcément désalloué correctement.
Depuis, j'ai gagné en mémoire et en performances.
Je t'invite à regarder la nouvelle démo, elle fourmille de graphismes variés et reste fluide malgré la charge graphique.
Pour info, il est possible de connaitre à l'execution les references sur une instance avec la version 3.3 d'eclipse.
Merci pour l'info, vive Eclipse !