Pourquoi voudriez-vous jamais implémenter finalize ()?


371

J'ai lu beaucoup de questions sur les recrues Java finalize()et je trouve déconcertant que personne n'ait vraiment expliqué clairement que finalize () est un moyen peu fiable de nettoyer les ressources. J'ai vu quelqu'un commenter qu'il l'utilisait pour nettoyer les connexions, ce qui est vraiment effrayant car la seule façon de se rapprocher le plus d'une garantie de fermeture d'une connexion est d'implémenter finalement try (catch).

Je n'étais pas scolarisé en CS, mais je programme en Java professionnellement depuis près d'une décennie maintenant et je n'ai jamais vu personne implémenter finalize()dans un système de production. Cela ne signifie toujours pas qu'il n'a pas ses utilisations ou que les gens avec qui j'ai travaillé l'ont bien fait.

Donc ma question est, quels sont les cas d'utilisation pour l'implémentation finalize()qui ne peuvent pas être traités de manière plus fiable via un autre processus ou syntaxe dans le langage?

Veuillez fournir des scénarios spécifiques ou votre expérience, simplement répéter un manuel Java ou finaliser l'utilisation prévue n'est pas suffisant, comme ce n'est pas le but de cette question.


La méthode HI finalize () est très bien expliquée ici howtodoinjava.com/2012/10/31/…
Sameer Kazi

2
La plupart des codes d'application et de bibliothèque ne seront jamais utilisés finalize(). Cependant, le code de bibliothèque de plate - forme , tel que SocketInputStream, qui gère les ressources natives au nom de l'appelant, le fait pour essayer de minimiser le risque de fuites de ressources (ou utilise des mécanismes équivalents, tels que PhantomReference, qui ont été ajoutés plus tard.) Donc, l'écosystème en a besoin , même si 99,9999% des développeurs n'en écriront jamais.
Brian Goetz

Réponses:


230

Vous pouvez l'utiliser comme backstop pour un objet contenant une ressource externe (socket, fichier, etc.). Implémentez une close()méthode et documentez-la.

Implémentez finalize()pour effectuer le close()traitement si vous détectez que cela n'a pas été fait. Peut-être avec quelque chose sous-jacent pour stderrsouligner que vous nettoyez après un appelant buggy.

Il offre une sécurité supplémentaire dans une situation exceptionnelle / buggy. Tous les appelants ne feront pas le bon travail à try {} finally {}chaque fois. Malheureux, mais vrai dans la plupart des environnements.

Je suis d'accord que c'est rarement nécessaire. Et comme le soulignent les commentateurs, il est livré avec des frais généraux GC. Utilisez uniquement si vous avez besoin de la sécurité «ceinture et bretelles» dans une application de longue durée.

Je vois qu'à partir de Java 9, Object.finalize()c'est obsolète! Ils nous indiquent java.lang.ref.Cleaneret java.lang.ref.PhantomReferencecomme alternatives.


44
Assurez-vous simplement qu'une exception n'est jamais levée par finalize (), sinon le garbage collector ne poursuivra pas le nettoyage de cet objet et vous obtiendrez une fuite de mémoire.
skaffman

17
Finalize n'est pas garanti d'être appelé, alors ne comptez pas sur la libération d'une ressource.
flicken

19
skaffman - Je ne le crois pas (à part une implémentation JVM buggée). À partir du javadoc Object.finalize (): si une exception non interceptée est levée par la méthode finalize, l'exception est ignorée et la finalisation de cet objet se termine.
John M

4
Votre proposition peut masquer les bugs des peuples (ne pas appeler close) pendant longtemps. Je ne recommanderais pas de le faire.
kohlerm

64
J'ai effectivement utilisé finaliser pour cette raison exacte. J'ai hérité du code et il était très bogué et avait tendance à laisser les connexions à la base de données ouvertes. Nous avons modifié les connexions pour qu'elles contiennent des données sur le moment et l'endroit où elles ont été créées, puis nous avons implémenté la finalisation pour enregistrer ces informations si la connexion n'était pas correctement fermée. Il s'est avéré être d'une grande aide pour retrouver les connexions non fermées.
brainimus

173

finalize()est un indice pour la JVM qu'il peut être intéressant d'exécuter votre code à un moment non spécifié. C'est une bonne chose lorsque vous voulez que le code échoue mystérieusement.

Faire quoi que ce soit d'important dans les finaliseurs (essentiellement tout sauf la journalisation) est également bon dans trois situations:

  • vous voulez parier que d'autres objets finalisés seront toujours dans un état que le reste de votre programme considère comme valide.
  • vous voulez ajouter beaucoup de code de vérification à toutes les méthodes de toutes vos classes qui ont un finaliseur, pour vous assurer qu'elles se comportent correctement après la finalisation.
  • vous voulez ressusciter accidentellement des objets finalisés et passer beaucoup de temps à essayer de comprendre pourquoi ils ne fonctionnent pas et / ou pourquoi ils ne sont pas finalisés lorsqu'ils sont finalement libérés.

Si vous pensez que vous avez besoin de finalize (), parfois ce que vous voulez vraiment est une référence fantôme (qui dans l'exemple donné pourrait contenir une référence dure à une connexion utilisée par son referand, et la fermer après que la référence fantôme a été mise en file d'attente). Cela a également la propriété de ne jamais s'exécuter mystérieusement, mais au moins, il ne peut pas appeler de méthodes ou ressusciter des objets finalisés. Donc, c'est juste pour les situations où vous n'avez pas absolument besoin de fermer cette connexion proprement, mais vous le souhaitez, et les clients de votre classe ne peuvent pas ou ne veulent pas appeler close eux-mêmes (ce qui est en fait assez juste - quoi' s l'intérêt d'avoir un garbage collector si vous concevez des interfaces qui nécessitent une action spécifique avant la collecte? Cela nous ramène au temps de malloc / free.)

D'autres fois, vous avez besoin de la ressource que vous pensez pouvoir gérer pour être plus robuste. Par exemple, pourquoi avez-vous besoin de fermer cette connexion? Il doit finalement être basé sur une sorte d'E / S fournies par le système (socket, fichier, etc.), alors pourquoi ne pouvez-vous pas compter sur le système pour le fermer pour vous lorsque le niveau de ressource le plus bas est obtenu? Si le serveur à l'autre extrémité vous oblige absolument à fermer la connexion proprement plutôt que de simplement laisser tomber la prise, alors que va-t-il se passer quand quelqu'un trébuche sur le câble d'alimentation de la machine sur laquelle votre code fonctionne, ou que le réseau intervenant s'éteint?

Avertissement: J'ai travaillé sur une implémentation JVM dans le passé. Je déteste les finaliseurs.


76
Vote pour une justice extrême contre le sarcasme.
Perception

32
Cela ne répond pas à la question; il indique simplement tous les inconvénients de finalize()sans donner de raisons à quelqu'un de vouloir l'utiliser
Escargot mécanique

1
@supercat: des références fantômes (d'ailleurs toutes les références) ont été introduites dans Java 2
Steve Jessop

1
@supercat: désolé oui, par "références" je voulais dire java.lang.ref.Referenceet ses sous-classes. Java 1 avait des variables :-) Et oui, il y avait aussi des finaliseurs.
Steve Jessop

2
@SteveJessop: Si un constructeur de classe de base demande à d'autres entités de modifier leur état au nom de l'objet en construction (modèle très courant), mais qu'un initialiseur de champ de classe dérivée lève une exception, l'objet partiellement construit doit immédiatement nettoyer après lui-même (c'est-à-dire informer ces entités externes que leurs services ne sont plus nécessaires), mais ni Java ni .NET ne proposent un modèle de nettoyage à distance même par lequel cela peut se produire. En effet, ils semblent faire tout leur possible pour rendre difficile une référence à la chose qui a besoin de nettoyage pour atteindre le code de nettoyage.
supercat

57

Une règle simple: ne jamais utiliser de finaliseurs.

Le seul fait qu'un objet possède un finaliseur (quel que soit le code qu'il exécute) est suffisant pour entraîner une surcharge considérable pour la collecte des ordures.

Extrait d'un article de Brian Goetz:

Les objets avec finaliseurs (ceux qui ont une méthode non triviale finalize ()) ont un surdébit significatif par rapport aux objets sans finaliseurs et doivent être utilisés avec parcimonie. Les objets finalisables sont à la fois plus lents à allouer et plus lents à collecter. Au moment de l'allocation, la JVM doit enregistrer tous les objets finalisables auprès du garbage collector et (au moins dans l'implémentation HotSpot JVM) les objets finalisables doivent suivre un chemin d'allocation plus lent que la plupart des autres objets. De même, les objets finalisables sont également plus lents à collecter. Il faut au moins deux cycles de récupération de place (dans le meilleur des cas) avant qu'un objet finalisable puisse être récupéré, et le garbage collector doit faire un travail supplémentaire pour appeler le finaliseur. Le résultat est plus de temps passé à allouer et collecter des objets et plus de pression sur le ramasse-miettes, car la mémoire utilisée par les objets finalisables inaccessibles est conservée plus longtemps. Combinez cela avec le fait que les finaliseurs ne sont pas garantis pour fonctionner dans un délai prévisible, ou même pas du tout, et vous pouvez voir qu'il y a relativement peu de situations pour lesquelles la finalisation est le bon outil à utiliser.


46

La seule fois où j'ai utilisé finaliser dans le code de production était d'implémenter une vérification que les ressources d'un objet donné avaient été nettoyées, et sinon, d'enregistrer un message très vocal. Il n'a pas vraiment essayé de le faire lui-même, il a juste crié beaucoup s'il n'était pas fait correctement. S'est avéré très utile.


34

Je fais du Java professionnellement depuis 1998 et je ne l'ai jamais implémenté finalize(). Pas une fois.


Comment puis-je détruire l'objet de classe publique? Cela cause un problème dans l'ADF. Fondamentalement, il est supposé recharger la page (obtenir de nouvelles données à partir d'une API), mais il prend l'ancien objet et affiche les résultats
InfantPro'Aravind '

2
@ InfantPro'Aravind 'appelant finalize ne supprime pas l'objet. C'est une méthode que vous implémentez que la JVM peut choisir d'appeler si et quand elle récupère votre objet.
Paul Tomblin

@PaulTomblin, ohk merci pour ce détail. Une suggestion de rafraîchissement d'une page sur ADF?
InfantPro'Aravind '

@ InfantPro'Aravind 'Aucune idée, jamais utilisé ADF.
Paul Tomblin

28

La réponse acceptée est bonne, je voulais juste ajouter qu'il y a maintenant un moyen d'avoir la fonctionnalité de finaliser sans vraiment l'utiliser du tout.

Regardez les classes "Référence". Référence faible, référence fantôme et référence souple.

Vous pouvez les utiliser pour garder une référence à tous vos objets, mais cette référence SEULE n'arrêtera pas GC. La chose intéressante à ce sujet est que vous pouvez le faire appeler une méthode quand il sera supprimé, et cette méthode peut être garantie d'être appelée.

Quant à finaliser: j'ai utilisé finaliser une fois pour comprendre quels objets étaient libérés. Vous pouvez jouer à des jeux soignés avec des statistiques, le comptage des références et autres - mais c'était uniquement pour l'analyse, mais faites attention au code comme celui-ci (pas seulement dans la finalisation, mais c'est là que vous êtes le plus susceptible de le voir):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

C'est un signe que quelqu'un ne savait pas ce qu'il faisait. Un "nettoyage" comme celui-ci n'est pratiquement jamais nécessaire. Lorsque la classe est GC'd, cela se fait automatiquement.

Si vous trouvez du code comme celui-ci dans une version finale, il est garanti que la personne qui l'a écrit est confuse.

Si c'est ailleurs, il se peut que le code soit un correctif valide pour un mauvais modèle (une classe reste longtemps et pour une raison quelconque, les choses auxquelles elle fait référence doivent être libérées manuellement avant que l'objet ne soit GC). Généralement, c'est parce que quelqu'un a oublié de supprimer un auditeur ou quelque chose et ne peut pas comprendre pourquoi son objet n'est pas GC, il supprime simplement les choses auxquelles il fait référence, hausse les épaules et s'en va.

Il ne devrait jamais être utilisé pour nettoyer les choses "plus rapidement".


3
Je pense que les références fantômes sont un meilleur moyen de savoir quels objets sont libérés.
Bill Michell

2
voter pour avoir fourni la meilleure explication de «référence faible» que j'aie jamais entendue.
sscarduzio

Je suis désolé que cela m'ait pris autant d'années pour lire ceci, mais c'est vraiment un bon ajout aux réponses.
Spencer Kormos

27

Je ne sais pas ce que vous pouvez en faire, mais ...

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

Je suppose donc que le Soleil a trouvé des cas où (ils pensent) qu'il devrait être utilisé.


12
Je suppose que c'est aussi une question de "le développeur Sun aura-t-il toujours raison"?
CodeReaper

13
Mais combien de ces utilisations implémentent ou testent simplement le support finalizeplutôt que de l'utiliser réellement?
Escargot mécanique

J'utilise Windows. Comment feriez-vous la même chose sous Windows?
gparyani

2
@damryfbfnetsi: Essayez d'installer ack ( Beyondgrep.com ) via chocolatey.org/packages/ack . Ou utilisez une simple fonction Rechercher dans les fichiers dans $FAVORITE_IDE.
Brian Cline

21
class MyObject {
    Test main;

    public MyObject(Test t) {    
        main = t; 
    }

    protected void finalize() {
        main.ref = this; // let instance become reachable again
        System.out.println("This is finalize"); //test finalize run only once
    }
}

class Test {
    MyObject ref;

    public static void main(String[] args) {
        Test test = new Test();
        test.ref = new MyObject(test);
        test.ref = null; //MyObject become unreachable,finalize will be invoked
        System.gc(); 
        if (test.ref != null) System.out.println("MyObject still alive!");  
    }
}

====================================

résultat:

This is finalize

MyObject still alive!

=====================================

Vous pouvez donc rendre une instance inaccessible accessible dans la méthode finalize.


21
Pour une raison quelconque, ce code me fait penser aux films de zombies. Vous GC votre objet et puis il se redresse ...
steveha

ci-dessus est de bons codes. Je me demandais comment rendre l'objet à nouveau accessible à l'instant.
lwpro2

15
non, ci-dessus est de mauvais codes. c'est un exemple de la raison pour laquelle la finalisation est mauvaise.
Tony

N'oubliez pas que l'invocation System.gc();n'est pas une garantie que le ramasse-miettes fonctionnera réellement. Il est à l'entière discrétion du GC de nettoyer le tas (peut-être encore assez de tas libre, peut-être pas trop fragmenté, ...). Ce n'est donc qu'une humble demande du programmeur "s'il vous plaît, pourriez-vous m'écouter et courir?" et pas une commande "exécuter maintenant comme je dis!". Désolé d'utiliser des mots linguaux, mais cela indique exactement comment fonctionne le GC de la JVM.
Roland

11

finalize()peut être utile pour détecter les fuites de ressources. Si la ressource doit être fermée mais n'écrit pas le fait qu'elle n'a pas été fermée dans un fichier journal et fermez-le. De cette façon, vous supprimez la fuite de ressources et vous donnez un moyen de savoir qu'elle s'est produite afin que vous puissiez la corriger.

Je programme en Java depuis la version 1.0 alpha 3 (1995) et je n'ai pas encore annulé la finalisation pour quoi que ce soit ...


6

Vous ne devez pas dépendre de finalize () pour nettoyer vos ressources à votre place. finalize () ne fonctionnera pas tant que la classe n'aura pas été récupérée, si c'est le cas. Il est préférable de libérer explicitement les ressources lorsque vous avez fini de les utiliser.


5

Pour mettre en évidence un point dans les réponses ci-dessus: les finaliseurs seront exécutés sur le seul thread GC. J'ai entendu parler d'une démo majeure de Sun où les développeurs ont ajouté un petit sommeil à certains finaliseurs et ont intentionnellement mis à genoux une démo 3D autrement fantaisiste.

Mieux vaut éviter, à l'exception possible des diagnostics test-env.

Eckel's Thinking in Java contient une bonne section à ce sujet.


4

Hmmm, je l'ai utilisé une fois pour nettoyer des objets qui n'étaient pas retournés dans un pool existant.

Ils ont été beaucoup circulés, il était donc impossible de dire quand ils pouvaient être ramenés en toute sécurité à la piscine. Le problème était qu'il introduisait une énorme pénalité lors de la collecte des ordures qui était bien plus importante que toute économie de mise en commun des objets. Il était en production depuis environ un mois avant que je ne déchire toute la piscine, que tout ne soit dynamique et que j'en ai fini avec.


4

Faites attention à ce que vous faites dans un finalize(). Surtout si vous l'utilisez pour des choses comme appeler close () pour vous assurer que les ressources sont nettoyées. Nous avons rencontré plusieurs situations où nous avions des bibliothèques JNI liées au code java en cours d'exécution et dans toutes les circonstances où nous avons utilisé finalize () pour appeler des méthodes JNI, nous obtenions une très mauvaise corruption de tas java. La corruption n'a pas été causée par le code JNI sous-jacent lui-même, toutes les traces de mémoire étaient correctes dans les bibliothèques natives. C'était juste le fait que nous appelions des méthodes JNI à partir de finalize ().

C'était avec un JDK 1.5 qui est encore largement utilisé.

Nous ne découvririons que quelque chose s'est mal passé que beaucoup plus tard, mais au final, le coupable était toujours la méthode finalize () utilisant les appels JNI.


3

Lors de l'écriture de code qui sera utilisé par d'autres développeurs qui nécessite une sorte de méthode de «nettoyage» pour être libéré afin de libérer des ressources. Parfois, ces autres développeurs oublient d'appeler votre méthode de nettoyage (ou de fermer, de détruire ou quoi que ce soit). Pour éviter d'éventuelles fuites de ressources, vous pouvez archiver la méthode finalize pour vous assurer que la méthode a été appelée et si ce n'est pas le cas, vous pouvez l'appeler vous-même.

De nombreux pilotes de base de données le font dans leurs implémentations Statement et Connection pour fournir un peu de sécurité contre les développeurs qui oublient d'appeler de près.


2

Edit: D'accord, cela ne fonctionne vraiment pas. Je l'ai implémenté et j'ai pensé que si cela échouait parfois, cela me convenait, mais il n'a même pas appelé la méthode finalize une seule fois.

Je ne suis pas un programmeur professionnel mais dans mon programme, j'ai un cas qui, à mon avis, est un bon exemple d'utilisation de finalize (), c'est-à-dire un cache qui écrit son contenu sur le disque avant qu'il ne soit détruit. Parce qu'il n'est pas nécessaire qu'il soit exécuté à chaque fois sur la destruction, cela ne fait qu'accélérer mon programme, j'espère que je ne l'ai pas mal fait.

@Override
public void finalize()
{
    try {saveCache();} catch (Exception e)  {e.printStackTrace();}
}

public void saveCache() throws FileNotFoundException, IOException
{
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
    out.writeObject(cache);
}

Je pense qu'un cache (du type que vous videriez sur le disque) est un bon exemple. Et même si finaliser n'est pas appelé, pas de sueur.
foo

2

Il peut être utile de supprimer des éléments qui ont été ajoutés à un emplacement global / statique (par nécessité) et doivent être supprimés lorsque l'objet est supprimé. Par exemple:

    void privé addGlobalClickListener () {
        faibleAwtEventListener = nouveau WeakAWTEventListener (this);

        Toolkit.getDefaultToolkit (). AddAWTEventListener (faibleAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @Passer outre
    void protégé finalize () jette Throwable {
        super.finalize ();

        if (faibleAwtEventListener! = null) {
            Toolkit.getDefaultToolkit (). RemoveAWTEventListener (faibleAwtEventListener);
        }
    }

Cela nécessite des efforts supplémentaires, car lorsque l'écouteur a une référence forte à l' thisinstance qui lui est transmise dans le constructeur, cette instance ne deviendra jamais inaccessible, donc le finalize()nettoyage ne sera jamais nettoyé de toute façon. Si, en revanche, l'auditeur détient une référence faible à cet objet, comme son nom l'indique, cette construction risque d'être nettoyée à des moments où elle ne devrait pas. Les cycles de vie des objets ne sont pas le bon outil pour implémenter la sémantique des applications.
Holger

0

iirc - vous pouvez utiliser la méthode de finalisation comme un moyen d'implémenter un mécanisme de mise en commun pour les ressources coûteuses - afin qu'ils n'obtiennent pas trop de GC.


0

En note:

Un objet qui remplace finalize () est traité spécialement par le garbage collector. Habituellement, un objet est immédiatement détruit pendant le cycle de collecte une fois que l'objet n'est plus dans la portée. Cependant, les objets finalisables sont plutôt déplacés vers une file d'attente, où des threads de finalisation séparés videront la file d'attente et exécuteront la méthode finalize () sur chaque objet. Une fois la méthode finalize () terminée, l'objet sera enfin prêt pour la récupération de place dans le cycle suivant.

Source: finalize () déconseillé sur java-9


0

Les ressources (fichier, socket, flux, etc.) doivent être fermées une fois que nous en avons fini avec elles. Ils ont généralement une close()méthode que nous appelons généralement dans la finallysection des try-catchdéclarations. quelquefoisfinalize() certains développeurs peuvent également utiliser IMO, mais ce n'est pas un moyen approprié car il n'y a aucune garantie que finalize sera toujours appelé.

Dans Java 7, nous avons une instruction try-with-resources qui peut être utilisée comme:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  // Processing and other logic here.
} catch (Exception e) {
  // log exception
} finally {
  // Just in case we need to do some stuff here.
}

Dans l'exemple ci-dessus, try-with-resource fermera automatiquement la ressource BufferedReaderen appelant la close()méthode. Si nous voulons, nous pouvons également implémenter Closeable dans nos propres classes et l'utiliser de la même manière. OMI, il semble plus soigné et simple à comprendre.


0

Personnellement, je n'ai presque jamais utilisé finalize()sauf dans une rare circonstance: j'ai fait une collection personnalisée de type générique et j'ai écrit une finalize()méthode personnalisée qui fait ce qui suit:

public void finalize() throws Throwable {
    super.finalize();
    if (destructiveFinalize) {
        T item;
        for (int i = 0, l = length(); i < l; i++) {
            item = get(i);
            if (item == null) {
                continue;
            }
            if (item instanceof Window) {
                ((Window) get(i)).dispose();
            }
            if (item instanceof CompleteObject) {
                ((CompleteObject) get(i)).finalize();
            }
            set(i, null);
        }
    }
}

( CompleteObjectEst une interface I fait qui vous permet de spécifier que vous avez mis rarement mis en œuvre des Objectméthodes telles que #finalize(), #hashCode()et #clone())

Ainsi, en utilisant une #setDestructivelyFinalizes(boolean)méthode sœur , le programme utilisant ma collection peut (aider) garantir que la destruction d'une référence à cette collection détruit également les références à son contenu et supprime toutes les fenêtres qui pourraient maintenir la JVM en vie involontairement. J'ai également envisagé d'arrêter tous les threads, mais cela a ouvert une toute nouvelle boîte de vers.


4
Faire appel finalize()à vos CompleteObjectinstances comme vous le faites dans le code ci-dessus implique qu'il pourrait être appelé deux fois, car la JVM pourrait toujours invoquer finalize()une fois que ces objets sont devenus inaccessibles, ce qui, en raison de la logique de finalisation, pourrait être avant la finalize()méthode de votre collection spéciale est appelé ou même en même temps. Comme je ne vois aucune mesure pour assurer la sécurité des threads dans votre méthode, vous semblez ne pas en être conscient…
Holger

0

La liste des réponses acceptées indique que la fermeture d'une ressource pendant la finalisation peut être effectuée.

Cependant, cette réponse montre qu'au moins dans java8 avec le compilateur JIT, vous rencontrez des problèmes inattendus où parfois le finaliseur est appelé avant même de terminer la lecture d'un flux géré par votre objet.

Donc, même dans cette situation, appeler finaliser ne serait pas recommandé.


Cela fonctionnerait, si l'opération est sécurisée pour les threads, ce qui est indispensable, car les finaliseurs peuvent être appelés par des threads arbitraires. Étant donné que la sécurité des threads correctement implémentée implique que non seulement l'opération de fermeture, mais aussi les opérations utilisant la ressource doivent se synchroniser sur le même objet ou verrou, il y aura une relation avant-avant entre l'utilisation et l'opération de fermeture, ce qui empêche également trop tôt finalisation. Mais, bien sûr, à un prix élevé pour toute l'utilisation…
Holger
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.