Je viens d'avoir un entretien, et on m'a demandé de créer un fuite de mémoire avec Java.
Inutile de dire que je me suis sentie très bête, n'ayant aucune idée de la façon de commencer à en créer une.
Quel serait un exemple ?
Je viens d'avoir un entretien, et on m'a demandé de créer un fuite de mémoire avec Java.
Inutile de dire que je me suis sentie très bête, n'ayant aucune idée de la façon de commencer à en créer une.
Quel serait un exemple ?
Voici un bon moyen de créer une véritable fuite de mémoire (objets inaccessibles par le code en cours d'exécution mais toujours stockés en mémoire) en Java pur :
ClassLoader
.new byte[1000000]
), stocke une référence forte à celui-ci dans un champ statique, puis stocke une référence à lui-même dans un champ statique. ThreadLocal
. L'allocation de la mémoire supplémentaire est facultative (la fuite de l'instance de la classe est suffisante), mais elle rendra la fuite encore plus rapide.ClassLoader
à partir duquel il a été chargé.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
donc après cela ThreadLocal
est collecté, son entrée est supprimée de la carte.ThreadLocal
qui est son clé cet objet ne sera ni collecté ni retiré de la carte tant que le thread sera en vie.Dans cet exemple, la chaîne de références fortes ressemble à ceci :
Thread
objet → threadLocals
map → instance de la classe d'exemple → classe d'exemple → statique. ThreadLocal
champ → ThreadLocal
objet.
(Le ClassLoader
ne joue pas vraiment un rôle dans la création de la fuite, elle ne fait qu'empirer la fuite à cause de cette chaîne de référence supplémentaire : exemple de la classe →. ClassLoader
→ toutes les classes qu'il a chargées. C'était encore pire dans de nombreuses implémentations de la JVM, notamment avant Java 7, car les classes et les ClassLoader
ont été alloués directement dans permgen et n'ont jamais été collectés dans les poubelles).
Une variation de ce schéma explique pourquoi les conteneurs d'applications (comme Tomcat) peuvent perdre de la mémoire comme une passoire si vous redéployez fréquemment des applications qui utilisent ThreadLocal
qui, d'une certaine manière, renvoient à eux-mêmes. Cela peut se produire pour un certain nombre de raisons subtiles et est souvent difficile à déboguer et/ou à réparer.
Mise à jour : Puisque beaucoup de gens continuent à le demander, voici un exemple de code qui montre ce comportement en action .
Champ statique contenant une référence à un objet [en particulier un final champ]
class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
Appel à String.intern()
sur une longue chaîne
String str = readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();
Flux ouverts (non fermés) (fichier, réseau, etc.)
try {
BufferedReader br = new BufferedReader(new FileReader(inputFile));
...
...
} catch (Exception e) {
e.printStacktrace();
}
Connexions non fermées
try {
Connection conn = ConnectionFactory.getConnection();
...
...
} catch (Exception e) {
e.printStacktrace();
}
Les zones qui sont inaccessibles au ramasseur de déchets de la JVM. comme la mémoire allouée par 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.
getServletContext().setAttribute("SOME_MAP", map);
Options JVM incorrectes ou inappropriées comme le noclassgc
sur le JDK d'IBM qui empêche la récupération des classes inutilisées.
Voir Paramètres du JDK d'IBM .
Une chose simple à faire est d'utiliser un HashSet avec une valeur incorrecte (ou inexistante) de la variable hashCode()
ou equals()
puis continuer à ajouter des "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 restent en place, vous pouvez utiliser un champ statique tel que
class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
Vous trouverez ci-dessous un cas non évident de fuite de Java, en plus du cas standard des listeners oubliés, des références statiques, des clés bidon/modifiables dans les hashmaps, ou simplement des threads bloqués sans aucune chance de terminer leur cycle de vie.
File.deleteOnExit()
- laisse toujours échapper la chaîne de caractères, char[]
donc la dernière ne s'applique pas ; @Daniel, pas besoin de votes, cependant.Je me concentrerai sur les threads pour montrer le danger des threads non gérés, je ne souhaite même pas aborder le swing.
Runtime.addShutdownHook
et non pas supprimer... et même avec removeShutdownHook, en raison d'un bogue dans la classe ThreadGroup concernant les threads non démarrés, il se peut qu'ils ne soient pas collectés, ce qui fait fuir le ThreadGroup. JGroup a la fuite dans GossipRouter.
Créer, mais pas démarrer, un Thread
entre dans la même catégorie que ci-dessus.
La création d'un thread hérite du ContextClassLoader
et AccessControlContext
plus le ThreadGroup
et tout InheritedThreadLocal
toutes ces références sont des fuites potentielles, tout comme les classes entières chargées par le chargeur de classes et toutes les références statiques, et ja-ja. L'effet est particulièrement visible avec l'ensemble du framework j.u.c.Executor qui comporte une fonction super simple de type ThreadFactory
Pourtant, la plupart des développeurs n'ont aucune idée du danger qui les guette. De plus, de nombreuses bibliothèques lancent des threads sur demande (beaucoup trop de bibliothèques populaires dans l'industrie).
ThreadLocal
caches ; ceux-ci sont diaboliques dans de nombreux cas. Je suis sûr que tout le monde a vu pas mal de caches simples basés sur ThreadLocal, et bien la mauvaise nouvelle : si le thread continue à aller plus loin que prévu la vie le contexte ClassLoader, c'est une pure petite fuite sympathique. N'utilisez pas les caches ThreadLocal à moins d'en avoir vraiment besoin.
Appel à ThreadGroup.destroy()
lorsque le ThreadGroup n'a pas de threads lui-même, mais qu'il conserve des ThreadGroups enfants. Une mauvaise fuite qui empêchera le ThreadGroup de se retirer de son parent, mais tous les enfants deviennent indénombrables.
Utilisation de WeakHashMap et la valeur (in)fait directement référence à la clé. C'est difficile à trouver sans un vidage du tas. Cela s'applique à tous les Weak/SoftReference
qui pourrait garder une référence dure à l'objet gardé.
Utilisation de java.net.URL
avec le protocole HTTP(S) et en chargeant la ressource depuis( !). Celui-ci est spécial, le KeepAliveCache
crée un nouveau thread dans le ThreadGroup du système qui fuit le classloader du contexte du thread actuel. Le thread est créé à la première demande lorsqu'il n'existe pas de thread vivant, donc vous pouvez avoir de la chance ou simplement fuir. Cette fuite est déjà corrigée dans Java 7 et le code qui crée le thread supprime correctement le classloader du contexte. Il existe quelques autres cas ( comme ImageFetcher , également fixé ) de créer des fils similaires.
Utilisation de InflaterInputStream
en passant par new java.util.zip.Inflater()
dans le constructeur ( PNGImageDecoder
par exemple) et ne pas appeler end()
du gonfleur. Eh bien, si vous passez dans le constructeur avec juste new
aucune chance... Et oui, appeler close()
sur le flux ne ferme pas l'inflateur s'il est passé manuellement comme paramètre du constructeur. Ce n'est pas une vraie fuite puisqu'elle sera libérée par le finaliseur... quand il le jugera nécessaire. Jusqu'à ce moment-là, il consomme de la mémoire native à tel point que Linux oom_killer peut tuer le processus en toute impunité. Le problème principal est que la finalisation en Java est très peu fiable et que G1 a empiré la situation jusqu'à 7.0.2. Morale de l'histoire : libérez les ressources natives dès que vous le pouvez ; le finalisateur 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 (maximum) et 8 niveaux de mémoire (9 est le maximum) allouant plusieurs centaines de Ko de mémoire native. Heureusement, Deflater
n'est pas largement utilisé et, à ma connaissance, le JDK ne contient aucun abus. Toujours appeler end()
si vous créez manuellement un Deflater
ou Inflater
. La meilleure partie des deux dernières : vous ne pouvez pas les trouver via les outils de profilage normaux disponibles.
(Je peux ajouter d'autres pertes de temps que j'ai rencontrées sur demande).
Bonne chance et soyez prudents ; les fuites sont diaboliques !
La réponse dépend entièrement de ce que l'enquêteur pensait demander.
Est-il possible en pratique de faire fuir Java ? Bien sûr que oui, et il existe de nombreux exemples dans les autres réponses.
Mais il y a de multiples méta-questions qui ont pu être posées ?
J'interprète votre méta-question comme étant "Quelle est la réponse que j'aurais pu utiliser dans cette situation d'entretien". Et donc, je vais me concentrer sur les compétences d'entretien plutôt que sur Java. Je pense que vous êtes plus susceptible de répéter la situation de ne pas connaître la réponse à une question lors d'un entretien que d'être dans une situation où vous avez besoin 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 les entretiens est d'apprendre à écouter activement les questions et à travailler avec l'intervieweur pour extraire son intention. Cela vous permettra non seulement de répondre à sa question de la manière qu'il souhaite, mais aussi de montrer que vous avez des compétences essentielles en matière de communication. Et lorsqu'il faudra choisir entre plusieurs développeurs de talent égal, j'engagerai toujours celui qui écoute, réfléchit et comprend avant de répondre.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.