Je viens d'avoir une interview et on m'a demandé de créer une fuite de mémoire avec Java.
Inutile de dire que je me sentais assez stupide de n'avoir aucune idée de comment commencer à en créer un.
Quel serait un exemple?
java
memory
memory-leaks
Mat B.
la source
la source
Réponses:
Voici un bon moyen de créer une véritable fuite de mémoire (objets inaccessibles en exécutant du code mais toujours stockés en mémoire) en Java pur:
ClassLoader
.new byte[1000000]
), stocke une référence forte à elle dans un champ statique, puis stocke une référence à elle-même dans aThreadLocal
. L'allocation de la mémoire supplémentaire est facultative (la fuite de l'instance de classe est suffisante), mais cela rendra la fuite beaucoup plus rapide.ClassLoader
source à partir de laquelle elle a été chargée.En raison de la façon dont
ThreadLocal
est implémenté dans le JDK d'Oracle, cela crée une fuite de mémoire:Thread
a un champ privéthreadLocals
, qui stocke en fait les valeurs locales du thread.ThreadLocal
objet, donc après que cetThreadLocal
objet a été récupéré, son entrée est supprimée de la carte.ThreadLocal
objet qui est sa clé , cet objet ne sera ni récupéré ni supprimé de la carte tant que le thread vivra.Dans cet exemple, la chaîne de références fortes ressemble à ceci:
Thread
objet →threadLocals
carte → instance de l'exemple de classe → exemple de classe →ThreadLocal
champ statique →ThreadLocal
objet.(Le
ClassLoader
ne joue pas vraiment un rôle dans la création de la fuite, il ne fait qu'aggraver la fuite à cause de cette chaîne de référence supplémentaire: exemple classe →ClassLoader
→ toutes les classes qu'il a chargées. C'était encore pire dans de nombreuses implémentations JVM, en particulier avant Java 7, car les classes et lesClassLoader
s ont été alloués directement dans permgen et n'ont jamais été récupérés.)Une variation de ce modèle est la raison pour laquelle les conteneurs d'applications (comme Tomcat) peuvent fuir la mémoire comme un tamis si vous redéployez fréquemment des applications qui utilisent des
ThreadLocal
s qui, d'une manière ou d'une autre, pointent vers elles-mêmes. Cela peut se produire pour un certain nombre de raisons subtiles et est souvent difficile à déboguer et / ou à corriger.Mise à jour : Étant donné que de nombreuses personnes continuent de le demander, voici un exemple de code qui montre ce comportement en action .
la source
Champ statique contenant la référence d'objet [esp champ final]
Appel
String.intern()
sur une longue chaîneFlux ouverts (non fermés) (fichier, réseau, etc.)
Connexions non fermées
Zones inaccessibles depuis le garbage collector de la JVM , telles que la mémoire allouée via des méthodes natives
Dans les applications Web, certains objets sont stockés dans la portée de l'application jusqu'à ce que l'application soit explicitement arrêtée ou supprimée.
Options JVM incorrectes ou inappropriées , telles que l'
noclassgc
option sur IBM JDK qui empêche le garbage collection inutiliséVoir Paramètres IBM jdk .
la source
close()
n'est généralement pas invoqué dans le finaliseur). thread car il peut s'agir d'une opération de blocage). C'est une mauvaise pratique de ne pas fermer, mais cela ne provoque pas de fuite. La java.sql.Connection non fermée est la même.intern
contenu de table de hachage. En tant que tel, il s'agit de déchets correctement récupérés et non d'une fuite. (mais IANAJP) mindprod.com/jgloss/interned.html#GCUne chose simple à faire est d'utiliser un HashSet avec un incorrect (ou inexistante)
hashCode()
ouequals()
, puis continuer à ajouter « doublons ». Au lieu d'ignorer les doublons comme il se doit, l'ensemble ne fera que croître et vous ne pourrez pas les supprimer.Si vous voulez que ces mauvaises clés / éléments traînent, vous pouvez utiliser un champ statique comme
la source
Ci-dessous, il y aura un cas non évident où Java fuit, en plus du cas standard d'écouteurs oubliés, de références statiques, de fausses clés / modifiables dans des hashmaps, ou simplement des threads coincés sans aucune chance de mettre fin à leur cycle de vie.
File.deleteOnExit()
- fuit toujours la chaîne,si la chaîne est une sous-chaîne, la fuite est encore pire (le caractère sous-jacent [] est également divulgué)- en Java 7, la sous-chaîne copie également lachar[]
, donc la dernière ne s'applique pas ; @Daniel, cependant, pas besoin de votes.Je vais me concentrer sur les threads pour montrer le danger des threads non gérés principalement, je ne souhaite même pas toucher le swing.
Runtime.addShutdownHook
et pas supprimer ... et même avec removeShutdownHook en raison d'un bogue dans la classe ThreadGroup concernant les threads non démarrés, il peut ne pas être collecté, ce qui fuit efficacement le ThreadGroup. JGroup a la fuite dans GossipRouter.Créer, mais pas démarrer, a
Thread
entre dans la même catégorie que ci-dessus.La création d'un thread hérite de
ContextClassLoader
etAccessControlContext
, ainsi que deThreadGroup
et anyInheritedThreadLocal
, toutes ces références sont des fuites potentielles, ainsi que les classes entières chargées par le chargeur de classe et toutes les références statiques, et ja-ja. L'effet est particulièrement visible avec l'ensemble du framework jucExecutor qui dispose d'uneThreadFactory
interface super simple , mais la plupart des développeurs n'ont aucune idée du danger qui se cache. De nombreuses bibliothèques démarrent également des threads sur demande (beaucoup trop de bibliothèques populaires de l'industrie).ThreadLocal
caches; ce sont des maux dans de nombreux cas. Je suis sûr que tout le monde a vu pas mal de caches simples basés sur ThreadLocal, enfin la mauvaise nouvelle: si le thread continue plus que prévu la vie dans le contexte ClassLoader, c'est une pure belle petite fuite. N'utilisez pas les caches ThreadLocal à moins que cela ne soit vraiment nécessaire.Appel
ThreadGroup.destroy()
lorsque le ThreadGroup n'a pas de threads lui-même, mais il conserve toujours les ThreadGroups enfants. Une mauvaise fuite qui empêchera le ThreadGroup de se retirer de son parent, mais tous les enfants deviennent non énumérables.L'utilisation de WeakHashMap et de la valeur (in) fait directement référence à la clé. C'est difficile à trouver sans vidage de tas. Cela s'applique à toutes les extensions
Weak/SoftReference
qui pourraient conserver une référence dure à l'objet gardé.Utilisation
java.net.URL
avec le protocole HTTP (S) et chargement de la ressource à partir de (!). Celui-ci est spécial, leKeepAliveCache
crée un nouveau thread dans le système ThreadGroup qui fuit le chargeur de classe de contexte du thread actuel. Le thread est créé à la première demande lorsqu'il n'existe aucun thread vivant, vous pouvez donc avoir de la chance ou simplement fuir. La fuite est déjà corrigée dans Java 7 et le code qui crée le thread supprime correctement le chargeur de classe de contexte. Il y a peu de cas supplémentaires (comme ImageFetcher, également corrigé ) de créer des threads similaires.Utiliser en
InflaterInputStream
passantnew java.util.zip.Inflater()
le constructeur (PNGImageDecoder
par exemple) et ne pas appelerend()
le gonfleur. Eh bien, si vous passez dans le constructeur avec justenew
, aucune chance ... Et oui, appelerclose()
sur le flux ne ferme pas le gonfleur s'il est passé manuellement en paramètre constructeur. Ce n'est pas une vraie fuite puisqu'elle serait publiée par le finaliseur ... quand elle le jugerait nécessaire. Jusqu'à ce moment, il mange si mal la mémoire native qu'il peut provoquer Linux oom_killer pour tuer le processus en toute impunité. Le principal problème est que la finalisation en Java est très peu fiable et G1 l'a aggravée jusqu'à 7.0.2. Morale de l'histoire: libérez les ressources natives dès que vous le pouvez; le finaliseur est tout simplement trop pauvre.Le même cas avec
java.util.zip.Deflater
. Celui-ci est bien pire car Deflater est gourmand en mémoire en Java, c'est-à-dire qu'il utilise toujours 15 bits (max) et 8 niveaux de mémoire (9 est max) allouant plusieurs centaines de Ko de mémoire native. Heureusement, ilDeflater
n'est pas largement utilisé et à ma connaissance, JDK ne contient aucun abus. Appelez toujoursend()
si vous créez manuellement unDeflater
ouInflater
. La meilleure partie des deux derniers: vous ne pouvez pas les trouver via les outils de profilage normaux disponibles.(Je peux ajouter des pertes de temps supplémentaires que j'ai rencontrées sur demande.)
Bonne chance et rester en sécurité; les fuites sont mauvaises!
la source
Creating but not starting a Thread...
Oui, j'ai été gravement mordu par celui-ci il y a quelques siècles! (Java 1.3)unstarted
nombre, mais cela empêche le groupe de threads de détruire (moindre mal mais toujours une fuite)ThreadGroup.destroy()
lorsque le ThreadGroup n'a pas de threads lui-même ..." est un bug incroyablement subtil; Je poursuis cela depuis des heures, induit en erreur parce que l'énumération du thread dans mon interface graphique de contrôle n'a rien montré, mais le groupe de threads et, probablement, au moins un groupe enfant ne s'en iraient pas.La plupart des exemples ici sont "trop complexes". Ce sont des cas marginaux. Avec ces exemples, le programmeur a fait une erreur (comme ne pas redéfinir equals / hashcode), ou a été mordu par un cas d'angle de la JVM / JAVA (chargement de classe avec statique ...). Je pense que ce n'est pas le type d'exemple qu'un enquêteur veut ni même le cas le plus courant.
Mais il existe des cas vraiment plus simples pour les fuites de mémoire. Le garbage collector ne libère que ce qui n'est plus référencé. En tant que développeurs Java, nous ne nous soucions pas de la mémoire. Nous l'allouons en cas de besoin et le laissons être libéré automatiquement. Bien.
Mais toute application de longue durée a tendance à avoir un état partagé. Il peut s'agir de n'importe quoi, de la statique, des singletons ... Souvent, les applications non triviales tendent à faire des graphiques d'objets complexes. Il suffit d'oublier de définir une référence sur null ou plus souvent d'oublier de supprimer un objet d'une collection est suffisant pour faire une fuite de mémoire.
Bien sûr, toutes sortes d'écouteurs (comme les écouteurs d'interface utilisateur), les caches ou tout état partagé de longue durée ont tendance à produire une fuite de mémoire s'ils ne sont pas correctement gérés. Ce qui doit être compris, c'est qu'il ne s'agit pas d'un cas d'angle Java ou d'un problème avec le garbage collector. C'est un problème de conception. Nous concevons que nous ajoutons un écouteur à un objet à longue durée de vie, mais nous ne supprimons pas l'écouteur lorsqu'il n'est plus nécessaire. Nous mettons en cache des objets, mais nous n'avons aucune stratégie pour les supprimer du cache.
Nous avons peut-être un graphe complexe qui stocke l'état précédent dont a besoin un calcul. Mais l'état précédent est lui-même lié à l'état d'avant et ainsi de suite.
Comme nous devons fermer les connexions ou les fichiers SQL. Nous devons définir des références appropriées sur null et supprimer des éléments de la collection. Nous aurons des stratégies de mise en cache appropriées (taille maximale de la mémoire, nombre d'éléments ou temporisateurs). Tous les objets qui permettent à un écouteur d'être notifié doivent fournir à la fois une méthode addListener et removeListener. Et lorsque ces notificateurs ne sont plus utilisés, ils doivent effacer leur liste d'auditeurs.
Une fuite de mémoire est en effet vraiment possible et parfaitement prévisible. Pas besoin de fonctionnalités linguistiques spéciales ou de cas d'angle. Les fuites de mémoire sont un indicateur que quelque chose manque peut-être ou même des problèmes de conception.
la source
WeakReference
) n'existe de l'un à l'autre. Si une référence d'objet avait un bit de rechange, il pourrait être utile d'avoir un indicateur "se soucie de la cible" ...PhantomReference
) s'il s'avère qu'un objet n'a personne qui s'en soucie.WeakReference
se rapproche quelque peu, mais doit être converti en une référence forte avant de pouvoir être utilisé; si un cycle GC se produit alors que la référence forte existe, la cible sera supposée utile.La réponse dépend entièrement de ce que l'intervieweur pensait demander.
Est-il possible en pratique de faire fuir Java? Bien sûr que oui, et il y a plein d'exemples dans les autres réponses.
Mais il y a plusieurs méta-questions qui ont pu être posées?
Je lis votre méta-question comme "Quelle réponse aurais-je pu utiliser dans cette situation d'entrevue". Et donc, je vais me concentrer sur les compétences d'entrevue au lieu de Java. Je crois que vous êtes plus susceptible de répéter la situation de ne pas connaître la réponse à une question dans une interview que vous ne devez être en mesure de savoir comment faire fuir Java. J'espère donc que cela vous aidera.
L'une des compétences les plus importantes que vous pouvez développer pour l'entretien consiste à apprendre à écouter activement les questions et à travailler avec l'enquêteur pour en extraire l'intention. Non seulement cela vous permet de répondre à leur question comme ils le souhaitent, mais cela montre également que vous avez des compétences de communication essentielles. Et quand il s'agit de choisir entre de nombreux développeurs tout aussi talentueux, j'engagerai celui qui écoute, pense et comprend avant de répondre à chaque fois.
la source
Ce qui suit est un exemple assez inutile, si vous ne comprenez pas JDBC . Ou au moins comment JDBC attend un développeur à proximité
Connection
,Statement
et desResultSet
cas avant de les jeter ou de perdre des références à eux, au lieu de compter sur la mise en œuvrefinalize
.Le problème avec ce qui précède est que l'
Connection
objet n'est pas fermé, et donc la connexion physique restera ouverte, jusqu'à ce que le ramasse-miettes se déplace et voit qu'il est inaccessible. GC invoquera lafinalize
méthode, mais il existe des pilotes JDBC qui n'implémentent pas lefinalize
, du moins pas de la même manière que celleConnection.close
implémentée. Le comportement résultant est que même si la mémoire est récupérée en raison de la collecte d'objets inaccessibles, les ressources (y compris la mémoire) associées à l'Connection
objet peuvent tout simplement ne pas être récupérées.Dans un tel cas où
Connection
des »finalize
va durer plusieurs cycles de collecte des ordures, jusqu'à ce que le serveur de base de données figure finalement que la connexion n'est pas en vie la méthode fait tout up pas propre, on peut effectivement constater que la connexion physique au serveur de base de données (si elle ne) et doit être fermé.Même si le pilote JDBC devait être implémenté
finalize
, il est possible que des exceptions soient levées lors de la finalisation. Le comportement résultant est que toute mémoire associée à l'objet désormais "dormant" ne sera pas récupérée, car ilfinalize
est garanti qu'elle ne sera invoquée qu'une seule fois.Le scénario ci-dessus de rencontrer des exceptions lors de la finalisation de l'objet est lié à un autre autre scénario qui pourrait éventuellement conduire à une fuite de mémoire - la résurrection des objets. La résurrection d'objet se fait souvent intentionnellement en créant une référence forte à l'objet en cours de finalisation, à partir d'un autre objet. Lorsque la résurrection d'objets est mal utilisée, elle entraînera une fuite de mémoire en combinaison avec d'autres sources de fuites de mémoire.
Il existe de nombreux autres exemples que vous pouvez évoquer - comme
List
instance où vous ne faites qu'ajouter à la liste sans en supprimer (bien que vous devriez vous débarrasser des éléments dont vous n'avez plus besoin), ouSocket
s ouFile
s, mais ne pas les fermer lorsqu'ils ne sont plus nécessaires (similaire à l'exemple ci-dessus impliquant leConnection
classe).la source
Connection.close
dans le bloc enfin tous mes appels SQL. Pour plus de plaisir, j'ai appelé des procédures stockées Oracle de longue durée qui nécessitaient des verrous côté Java pour éviter trop d'appels à la base de données.L'implémentation d'ArrayList.remove (int) est probablement l'un des exemples les plus simples d'une fuite de mémoire potentielle, et comment l'éviter.
Si vous l'aviez implémenté vous-même, auriez-vous pensé à supprimer l'élément de tableau qui n'est plus utilisé (
elementData[--size] = null
)? Cette référence pourrait garder un énorme objet en vie ...la source
Chaque fois que vous conservez des références à des objets dont vous n'avez plus besoin, vous avez une fuite de mémoire. Voir Gestion des fuites de mémoire dans les programmes Java pour des exemples de la façon dont les fuites de mémoire se manifestent en Java et ce que vous pouvez faire à ce sujet.
la source
...then the question of "how do you create a memory leak in X?" becomes meaningless, since it's possible in any language.
Je ne vois pas comment vous tirez cette conclusion. Il existe moins de façons de créer une fuite de mémoire en Java, quelle que soit la définition. C'est définitivement une question valable.Vous pouvez faire une fuite de mémoire avec la classe sun.misc.Unsafe . En fait, cette classe de service est utilisée dans différentes classes standard (par exemple dans les classes java.nio ). Vous ne pouvez pas créer directement une instance de cette classe , mais vous pouvez utiliser la réflexion pour le faire .
Le code ne se compile pas dans Eclipse IDE - compilez-le à l'aide de la commande
javac
(pendant la compilation, vous obtiendrez des avertissements)la source
Je peux copier ma réponse à partir d'ici: le moyen le plus simple de provoquer une fuite de mémoire en Java?
"Une fuite de mémoire, en informatique (ou fuite, dans ce contexte), se produit lorsqu'un programme informatique consomme de la mémoire mais ne parvient pas à la restituer au système d'exploitation." (Wikipédia)
La réponse est simple: vous ne pouvez pas. Java gère automatiquement la mémoire et libère les ressources dont vous n'avez pas besoin. Vous ne pouvez pas empêcher cela de se produire. Il sera TOUJOURS en mesure de libérer les ressources. Dans les programmes avec gestion manuelle de la mémoire, c'est différent. Vous ne pouvez pas obtenir de mémoire en C en utilisant malloc (). Pour libérer la mémoire, vous avez besoin du pointeur retourné par malloc et appelez free () dessus. Mais si vous n'avez plus le pointeur (écrasé ou durée de vie dépassée), vous êtes malheureusement incapable de libérer cette mémoire et vous avez donc une fuite de mémoire.
Jusqu'à présent, toutes les autres réponses ne sont pas vraiment des fuites de mémoire dans ma définition. Ils visent tous à remplir la mémoire de choses inutiles très rapidement. Mais à tout moment, vous pouvez toujours déréférencer les objets que vous avez créés et libérer ainsi la mémoire -> PAS DE FUITE. la réponse d'Acconrad est assez proche, comme je dois l'admettre, car sa solution consiste à "écraser" simplement le garbage collector en le forçant dans une boucle sans fin).
La réponse longue est: vous pouvez obtenir une fuite de mémoire en écrivant une bibliothèque pour Java à l'aide du JNI, qui peut avoir une gestion manuelle de la mémoire et donc avoir des fuites de mémoire. Si vous appelez cette bibliothèque, votre processus java perdra de la mémoire. Ou, vous pouvez avoir des bogues dans la JVM, de sorte que la JVM perd de la mémoire. Il y a probablement des bogues dans la JVM, il peut même y en avoir quelques-uns connus, car le ramasse-miettes n'est pas si simple, mais c'est quand même un bogue. De par sa conception, cela n'est pas possible. Vous demandez peut-être du code java affecté par un tel bogue. Désolé, je n'en connais pas et il pourrait bien ne plus être un bug dans la prochaine version Java.
la source
En voici une simple / sinistre via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 .
Étant donné que la sous-chaîne fait référence à la représentation interne de la chaîne d'origine, beaucoup plus longue, l'original reste en mémoire. Ainsi, tant que vous avez un StringLeaker en jeu, vous avez également toute la chaîne d'origine en mémoire, même si vous pensez peut-être que vous vous accrochez à une chaîne à un seul caractère.
La façon d'éviter de stocker une référence indésirable à la chaîne d'origine est de faire quelque chose comme ceci:
Pour plus de méchanceté, vous pouvez également
.intern()
utiliser la sous-chaîne:Cela gardera à la fois la chaîne longue d'origine et la sous-chaîne dérivée en mémoire même après que l'instance de StringLeaker a été supprimée.
la source
muchSmallerString
est libéré (car l'StringLeaker
objet est détruit), la longue chaîne sera également libérée. Ce que j'appelle une fuite de mémoire est une mémoire qui ne peut jamais être libérée dans cette instance de JVM. Cependant, vous vous avez montré comment libérer la mémoire:this.muchSmallerString=new String(this.muchSmallerString)
. Avec une vraie fuite de mémoire, vous ne pouvez rien faire.intern
cas peut être plus une «surprise de mémoire» qu'une «fuite de mémoire»..intern()
Cependant, la sous-chaîne crée certainement une situation où la référence à la chaîne plus longue est préservée et ne peut pas être libérée.Un exemple courant de cela dans le code GUI est lors de la création d'un widget / composant et l'ajout d'un écouteur à un objet de portée statique / d'application, puis ne supprimant pas l'écouteur lorsque le widget est détruit. Non seulement vous obtenez une fuite de mémoire, mais aussi une baisse de performance comme lorsque tout ce que vous écoutez se déclenche, tous vos anciens auditeurs sont également appelés.
la source
Prenez n'importe quelle application Web exécutée dans n'importe quel conteneur de servlet (Tomcat, Jetty, Glassfish, peu importe ...). Redéployez l'application 10 ou 20 fois de suite (il peut suffire de toucher simplement le WAR dans le répertoire de déploiement automatique du serveur.
À moins que quiconque n'ait réellement testé cela, il y a de fortes chances que vous obteniez une OutOfMemoryError après quelques redéploiements, car l'application n'a pas pris soin de nettoyer après elle-même. Vous pouvez même trouver un bogue sur votre serveur avec ce test.
Le problème est que la durée de vie du conteneur est plus longue que la durée de vie de votre application. Vous devez vous assurer que toutes les références que le conteneur peut avoir aux objets ou aux classes de votre application peuvent être récupérées.
S'il n'y a qu'une seule référence survivant au déploiement de votre application Web, le chargeur de classe correspondant et par conséquent toutes les classes de votre application Web ne peuvent pas être récupérés.
Les threads démarrés par votre application, les variables ThreadLocal, les ajouts de journalisation sont certains des suspects habituels à l'origine de fuites du chargeur de classe.
la source
Peut-être en utilisant du code natif externe via JNI?
Avec Java pur, c'est presque impossible.
Mais il s'agit d'un type de fuite de mémoire "standard", lorsque vous ne pouvez plus accéder à la mémoire, mais elle appartient toujours à l'application. Vous pouvez plutôt conserver des références à des objets inutilisés ou ouvrir des flux sans les fermer par la suite.
la source
J'ai eu une belle "fuite de mémoire" en ce qui concerne l'analyse PermGen et XML une fois. L'analyseur XML que nous avons utilisé (je ne me souviens plus lequel était) a fait un String.intern () sur les noms de balises, pour accélérer la comparaison. Un de nos clients a eu la bonne idée de stocker des valeurs de données non pas dans des attributs XML ou du texte, mais sous forme de variables, nous avions donc un document comme:
En fait, ils n'utilisaient pas de chiffres mais des identifiants textuels plus longs (environ 20 caractères), qui étaient uniques et arrivaient à un rythme de 10 à 15 millions par jour. Cela fait 200 Mo de déchets par jour, ce qui n'est plus jamais nécessaire et jamais GCed (car il est dans PermGen). Nous avions fixé permgen à 512 Mo, il a donc fallu environ deux jours pour que l'exception de mémoire insuffisante (OOME) arrive ...
la source
Qu'est-ce qu'une fuite de mémoire:
Exemple typique:
Un cache d'objets est un bon point de départ pour gâcher les choses.
Votre cache grandit et grandit. Et très bientôt, la base de données entière est aspirée en mémoire. Une meilleure conception utilise un LRUMap (conserve uniquement les objets récemment utilisés dans le cache).
Bien sûr, vous pouvez rendre les choses beaucoup plus compliquées:
Ce qui arrive souvent:
Si cet objet Info a des références à d'autres objets, qui ont à nouveau des références à d'autres objets. D'une certaine manière, vous pouvez également considérer qu'il s'agit d'une sorte de fuite de mémoire (causée par une mauvaise conception).
la source
J'ai trouvé intéressant que personne n'utilise les exemples de classe interne. Si vous avez une classe interne; il conserve de manière inhérente une référence à la classe conteneur. Bien sûr, il ne s'agit pas techniquement d'une fuite de mémoire car Java finira par la nettoyer; mais cela peut faire traîner les classes plus longtemps que prévu.
Maintenant, si vous appelez Example1 et obtenez un exemple2 en supprimant l'exemple1, vous aurez toujours un lien vers un objet Example1.
J'ai également entendu une rumeur selon laquelle si vous avez une variable qui existe depuis plus d'un certain temps; Java suppose qu'il existera toujours et n'essaiera jamais de le nettoyer s'il ne peut plus être atteint dans le code. Mais cela n'est absolument pas vérifié.
la source
J'ai récemment rencontré une situation de fuite de mémoire causée en quelque sorte par log4j.
Log4j possède ce mécanisme appelé Nested Diagnostic Context (NDC) qui est un instrument permettant de distinguer les sorties de journaux entrelacées de différentes sources. La granularité à laquelle le NDC fonctionne est les threads, il distingue donc séparément les sorties de journal des différents threads.
Afin de stocker des balises spécifiques au thread, la classe NDC de log4j utilise une table de hachage qui est saisie par l'objet Thread lui-même (par opposition à l'ID du thread), et donc jusqu'à ce que la balise NDC reste en mémoire tous les objets qui pendent du thread l'objet reste également en mémoire. Dans notre application Web, nous utilisons NDC pour baliser les sorties de journal avec un identifiant de demande pour distinguer les journaux d'une seule demande séparément. Le conteneur qui associe la balise NDC à un thread, la supprime également lors du renvoi de la réponse d'une demande. Le problème s'est produit lorsque, au cours du traitement d'une demande, un thread enfant a été généré, quelque chose comme le code suivant:
Un contexte NDC a donc été associé au thread en ligne généré. L'objet thread qui était la clé de ce contexte NDC, est le thread en ligne qui a l'objet énormeList suspendu. Par conséquent, même après que le thread a fini de faire ce qu'il faisait, la référence à l'énormeList a été maintenue vivante par le contexte NDC Hastable, provoquant ainsi une fuite de mémoire.
la source
L'intervieweur était probablement à la recherche d'une référence circulaire comme le code ci-dessous (qui ne laisse d'ailleurs passer que de la mémoire dans les très anciennes machines virtuelles Java qui utilisaient le comptage des références, ce qui n'est plus le cas). Mais c'est une question assez vague, c'est donc une excellente occasion de montrer votre compréhension de la gestion de la mémoire JVM.
Ensuite, vous pouvez expliquer qu'avec le comptage des références, le code ci-dessus entraînerait une fuite de mémoire. Mais la plupart des machines virtuelles Java modernes n'utilisent plus le comptage des références, la plupart utilisent un ramasse-miettes qui collectera en fait cette mémoire.
Ensuite, vous pourriez expliquer la création d'un objet qui a une ressource native sous-jacente, comme ceci:
Ensuite, vous pouvez expliquer qu'il s'agit techniquement d'une fuite de mémoire, mais en réalité, la fuite est causée par du code natif dans la JVM allouant des ressources natives sous-jacentes, qui n'ont pas été libérées par votre code Java.
À la fin de la journée, avec une JVM moderne, vous devez écrire du code Java qui alloue une ressource native en dehors de la portée normale de la reconnaissance de la JVM.
la source
Tout le monde oublie toujours la route du code natif. Voici une formule simple pour une fuite:
malloc
. N'appelle pasfree
.N'oubliez pas que les allocations de mémoire en code natif proviennent du tas JVM.
la source
Créez une carte statique et continuez d'y ajouter des références matérielles. Ceux-ci ne seront jamais GC'd.
la source
Vous pouvez créer une fuite de mémoire mobile en créant une nouvelle instance d'une classe dans la méthode finalize de cette classe. Points bonus si le finaliseur crée plusieurs instances. Voici un programme simple qui fuit le tas entier entre quelques secondes et quelques minutes selon la taille de votre tas:
la source
Je ne pense pas que quelqu'un l'ait encore dit: vous pouvez ressusciter un objet en remplaçant la méthode finalize () de telle sorte que finalize () stocke une référence de ceci quelque part. Le ramasse-miettes ne sera appelé qu'une seule fois sur l'objet, après quoi l'objet ne sera jamais détruit.
la source
finalize()
ne sera pas appelé mais l'objet sera collecté une fois qu'il n'y aura plus de références. Le garbage collector n'est pas "appelé" non plus.finalize()
méthode ne peut être appelée qu'une seule fois par la machine virtuelle Java, mais cela ne signifie pas qu'elle ne peut pas être récupérée à nouveau si l'objet est ressuscité, puis déréférencé à nouveau. S'il y a du code de fermeture de ressource dans lafinalize()
méthode, ce code ne sera pas exécuté à nouveau, cela peut provoquer une fuite de mémoire.J'ai récemment rencontré une sorte de fuite de ressources plus subtile. Nous ouvrons les ressources via getResourceAsStream du chargeur de classe et il s'est produit que les descripteurs de flux d'entrée n'étaient pas fermés.
Uhm, vous pourriez dire, quel idiot.
Eh bien, ce qui rend cela intéressant, c'est: de cette façon, vous pouvez divulguer la mémoire de tas du processus sous-jacent, plutôt que du tas de JVM.
Tout ce dont vous avez besoin est un fichier jar avec un fichier à l'intérieur qui sera référencé à partir du code Java. Plus le fichier jar est grand, plus la mémoire est allouée rapidement.
Vous pouvez facilement créer un tel pot avec la classe suivante:
Il suffit de coller dans un fichier nommé BigJarCreator.java, de le compiler et de l'exécuter à partir de la ligne de commande:
Et voilà: vous trouvez une archive jar dans votre répertoire de travail actuel avec deux fichiers à l'intérieur.
Créons une deuxième classe:
Cette classe ne fait rien, mais crée des objets InputStream non référencés. Ces objets seront immédiatement récupérés et ne contribuent donc pas à la taille du tas. Il est important pour notre exemple de charger une ressource existante à partir d'un fichier jar, et la taille importe ici!
En cas de doute, essayez de compiler et de démarrer la classe ci-dessus, mais assurez-vous de choisir une taille de segment de mémoire décente (2 Mo):
Vous ne rencontrerez pas d'erreur OOM ici, car aucune référence n'est conservée, l'application continuera à fonctionner quelle que soit la taille que vous avez choisie ITERATIONS dans l'exemple ci-dessus. La consommation de mémoire de votre processus (visible en haut (RES / RSS) ou explorateur de processus) augmente à moins que l'application accède à la commande d'attente. Dans la configuration ci-dessus, il allouera environ 150 Mo de mémoire.
Si vous souhaitez que l'application fonctionne en toute sécurité, fermez le flux d'entrée là où il a été créé:
et votre processus ne dépassera pas 35 Mo, indépendamment du nombre d'itérations.
Assez simple et surprenant.
la source
Comme beaucoup de gens l'ont suggéré, les fuites de ressources sont assez faciles à provoquer - comme les exemples JDBC. Les fuites de mémoire réelles sont un peu plus difficiles - surtout si vous ne comptez pas sur des bits cassés de la JVM pour le faire pour vous ...
Les idées de créer des objets qui ont une très grande empreinte et de ne pas pouvoir y accéder ne sont pas non plus de véritables fuites de mémoire. Si rien ne peut y accéder, il sera récupéré et si quelque chose peut y accéder, ce n'est pas une fuite ...
Cependant, une façon qui fonctionnait auparavant - et je ne sais pas si c'est toujours le cas - est d'avoir une chaîne circulaire à trois profondeurs. Comme dans l'objet A a une référence à l'objet B, l'objet B a une référence à l'objet C et l'objet C a une référence à l'objet A. Le GC était assez intelligent pour savoir qu'une chaîne à deux profonds - comme dans A <--> B - peut être collecté en toute sécurité si A et B ne sont accessibles par rien d'autre, mais ne peuvent pas gérer la chaîne à trois voies ...
la source
Une autre façon de créer des fuites de mémoire potentiellement énormes consiste à conserver les références à
Map.Entry<K,V>
aTreeMap
.Il est difficile d'évaluer pourquoi cela ne s'applique qu'à
TreeMap
s, mais en regardant l'implémentation, la raison pourrait être que: aTreeMap.Entry
stocke les références à ses frères et sœurs, donc si aTreeMap
est prêt à être collecté, mais une autre classe détient une référence à l'un des sonMap.Entry
, puis la carte entière sera conservée en mémoire.Scénario réel:
Imaginez avoir une requête db qui renvoie une
TreeMap
structure de Big Data. Les gens utilisent généralementTreeMap
s car l'ordre d'insertion des éléments est conservé.Si la requête était appelée plusieurs fois et, pour chaque requête (donc, pour chaque
Map
retour), vous enregistrezEntry
quelque part, la mémoire ne cessait de croître.Considérez la classe wrapper suivante:
Application:
Après chaque
pseudoQueryDatabase()
appel, lesmap
instances doivent être prêtes pour la collecte, mais cela ne se produira pas, car au moins uneEntry
est stockée ailleurs.Selon vos
jvm
paramètres, l'application peut se bloquer à un stade précoce en raison d'unOutOfMemoryError
.Vous pouvez voir sur ce
visualvm
graphique comment la mémoire continue de croître.La même chose ne se produit pas avec une structure de données hachée (
HashMap
).Il s'agit du graphique lors de l'utilisation d'un
HashMap
.La solution? Enregistrez simplement la clé / valeur (comme vous le faites probablement déjà) plutôt que d'enregistrer
Map.Entry
.J'ai écrit un point de référence plus complet ici .
la source
Les threads ne sont pas collectés avant la fin. Ils servent de racines à la collecte des ordures. Ils sont l'un des rares objets qui ne seront pas récupérés simplement en les oubliant ou en effaçant les références à eux.
Considérez: le modèle de base pour terminer un thread de travail consiste à définir une variable de condition vue par le thread. Le thread peut vérifier la variable périodiquement et l'utiliser comme signal pour terminer. Si la variable n'est pas déclarée
volatile
, la modification de la variable peut ne pas être vue par le thread, il ne saura donc pas se terminer. Ou imaginez si certains threads veulent mettre à jour un objet partagé, mais un blocage tout en essayant de le verrouiller.Si vous n'avez qu'une poignée de threads, ces bogues seront probablement évidents car votre programme cessera de fonctionner correctement. Si vous disposez d'un pool de threads qui crée plus de threads selon les besoins, les threads obsolètes / bloqués peuvent ne pas être remarqués et s'accumuleront indéfiniment, provoquant une fuite de mémoire. Les threads sont susceptibles d'utiliser d'autres données dans votre application, ce qui empêchera également la collecte de tout ce qu'ils référencent directement.
Comme exemple de jouet:
Appelez
System.gc()
tout ce que vous voulez, mais l'objet est passé àleakMe
ne mourra jamais.(*édité*)
la source
Je pense qu'un exemple valide pourrait être d'utiliser des variables ThreadLocal dans un environnement où les threads sont regroupés.
Par exemple, utiliser des variables ThreadLocal dans des servlets pour communiquer avec d'autres composants Web, avoir les threads créés par le conteneur et conserver ceux qui sont inactifs dans un pool. Les variables ThreadLocal, si elles ne sont pas correctement nettoyées, y resteront jusqu'à ce que, éventuellement, le même composant Web écrase leurs valeurs.
Bien sûr, une fois identifié, le problème peut être résolu facilement.
la source
L'intervieweur pourrait avoir recherché une solution de référence circulaire:
Il s'agit d'un problème classique avec les compteurs de déchets de comptage de référence. Vous expliqueriez alors poliment que les machines virtuelles Java utilisent un algorithme beaucoup plus sophistiqué qui n'a pas cette limitation.
-Wes Tarle
la source
first
n'est pas utile et doit être récupérée. Dans le comptage des références des récupérateurs de place, l'objet ne serait pas libéré car il y a une référence active sur lui (par lui-même). La boucle infinie est là pour démontrer la fuite: lorsque vous exécutez le programme, la mémoire augmentera indéfiniment.