Lorsque vous localisez des problèmes de performances JVM, vous pouvez rencontrer des fuites de mémoire et provoquer JVM OutOfMemory. Si le paramètre Reloadable = "True" est défini lorsque vous utilisez le conteneur TomCat, vous pouvez également rencontrer un débordement de mémoire lors du déploiement fréquemment des applications. Le principe du déploiement à chaud de Tomcat est de détecter que les fichiers du répertoire Web-Inf / Classes ou Web-Inf / Lib sont modifiés et que l'application sera arrêtée en premier, puis démarrée. Étant donné que Tomcat affecte un WebAppClassloader à chaque application par défaut, le principe de remplacement à chaud est de créer un nouveau Classloader pour charger la classe. Étant donné que le caractère unique d'une classe dans le JVM est déterminé par son fichier de classe et son chargeur de classe, le rechargement de la classe peut atteindre le but du remplacement à chaud. Lorsque le nombre de déploiements à chaud est plus fréquent, il provoquera plus de classes chargées par le JVM. Si la classe précédente n'est pas déchargée à temps pour une raison quelconque (comme les fuites de mémoire), cela peut conduire à une génération permanente ou à un métaspace outofMemory. Cet article présente brièvement le scénario où le fil de threadlocal et le chargeur de classe mène à des fuites de mémoire et finalement à l'ouvre-émmerie via une démo.
Désinstaller la classe
Une fois le cours utilisé, si la situation suivante est satisfaite, elle sera désinstallée:
1. Toutes les instances de cette classe dans le tas ont été recyclées, c'est-à-dire que les objets d'instance de cette classe n'existent pas dans le tas.
2. Le chargeur de classe chargement de cette classe a été recyclé.
3. L'objet de classe correspondant à cette classe ne peut être référencé nulle part, et l'objet de classe ne peut être accessible par réflexion.
Si la classe remplit les conditions de désinstallation, la JVM désinstalle la classe lorsqu'elle est en GC, c'est-à-dire effacer les informations de classe dans la zone de méthode.
Introduction de la scène
Dans l'article précédent, j'ai présenté le principe du threadlocal. Chaque fil a un threadlocalmap. Si le cycle de vie du fil est relativement long, l'entrée dans ThreadLocalmap peut ne pas être recyclée. L'objet threadlocal a toujours été fortement référencé par le thread. Étant donné que l'objet d'instance conservera la référence de l'objet de classe, l'objet de classe maintira la référence du Classloader qui le charge, ce qui fera décharger la classe. Lorsqu'il y a suffisamment de classes chargées, une génération permanente ou un débordement de mémoire métaspace peut se produire. Si la classe a de grands objets, comme un réseau d'octets plus grand, il entraînera le débordement de la mémoire de la zone de tas de Java.
Introduction du code source
Voici une classe interne intérieure. La classe intérieure a un objet threadlocal statique, qui est principalement utilisé pour faire en sorte que les threads contiennent des références solides à la classe intérieure, afin que la classe intérieure ne puisse pas être recyclée. Un chargeur de classe personnalisé est défini pour charger la classe intérieure, comme indiqué ci-dessous:
classe publique memoryleak {public static void main (String [] args) {// Parce que le thread s'exécute tout le temps, l'objet interne dans ThreadLocalmap a été fortement référencé par le thread nouveau thread (New Runnable () {@Override public void run () {while (true) {// chaque fois qu'une nouvelle instance de chargeur de classe est créée pour charge (Load1 ", Memoryleak.class.getClassloadher ()," com.ezlippi.memoryleak $ inner "," com.ezlippi.memoryleak $ inner "); GC pour le traitement de référence innerclass = null; } // Afin d'atteindre la zone de tas plus rapide de classe statique publique intérieure {octet privé [] mb = nouveau octet [1024 * 1024]; statique threadLocal <inner> threadLocal = new ThreadLocal <nner> () {@Override Protected Inner initialValue () {return new Inner (); }}; // pour appeler threadLocal.get () pour initialiser un objet interne statique {threadLocal.get (); } public inner () {}} // code source omet la classe statique privée personnalisée CustomClassOader étend classloader {}Débordement de mémoire de zone de tas
Afin de déclencher le débordement de la mémoire du tas, j'ai configuré un tableau d'octets de 1 Mo dans la classe intérieure, et en même temps, je dois appeler threadLocal.get () dans le bloc statique. Seul l'appel déclenchera InitialValue () pour initialiser un objet intérieur, sinon je vais simplement créer un objet ThreadLocal vide, et il n'y a pas de données dans le threadLocalmap.
Les paramètres JVM sont les suivants:
-Xms100m -xmx100m -xx: + useParNewgc -xx: + UseConcmarksweepgc -xx: + printgcdetails -xx: + printheapatgc -xx: + printclasshistogram -xx: + heapDumpOnouToLoryErrorrorror
Après les 814 dernières exécutions, la mémoire de la zone de tas JVM déborde, comme indiqué ci-dessous:
java.lang.outofMemoryError: tas de tas de tas de tas de tas à java_pid11824.hprof ... fichier de vidage de tas créé [100661202 octets en 1,501 sec] tas de nouvelle génération 30720K, utilisé 30389k [0x000000000f9c00000000000000000000000000000000fbd500000) Eden Space 27328, 0x0000000000f 99% utilisé [0x00000000F9C000000, 0x00000000FB6AD450, 0x00000000FB6B0000) de Space 3392K, 90% utilisé [0x00000000000000000000) pour l'espace 3392k, 0% utilisé [0x0000000000fba00000, 0x00000000fbd500000) concurrent mark-sweep generation total 68288K, used 67600K [0x000000000fbd500000, 0x0000001000000000, 0x000000010000000) Metaspace used 3770K, capacity 5134K, committed 5248K, reserved 1056768K class L'espace utilisé 474K, Capacité 578K, engagé 640k, réservé 1048576KException dans Thread "Thread-0" java.lang.outofMemoryError: Java Heap Space at com.ezlippi.memoryleak $ Inner. <Clinnit> (Memoryleak.java:34) Sun.Reflect.NativeConstructorAccessorimp.NewInstance0 (méthode indigène) sur Sun.Reflect.NativeConstructorAccessOrimp.Newinstance (Source inconnue) à Sun.Reflect.DelegatingConstructorAcCeSSORIMPL.Newinstance (Source inconnue) à Java.Lang.Reflect.Constructor.Newinwin (UNNERSANCE) java.lang.reflect.constructor.newinstance (source inconnue) Source) sur java.lang.class.newinstance (source inconnue) à com.ezlippi.memoryleak 1.run (Memoryleak.java:20) à java.lang.thred.run (Source inconnu)
Vous pouvez voir que le JVM n'a pas de mémoire pour créer un nouvel objet intérieur, car la zone de tas stocke de nombreux tableaux d'octets de 1 Mo. Ici, j'ai imprimé l'histogramme de la classe (la figure suivante est une scène avec une taille de tas de 1024 m), omettant des classes insignifiantes. On peut voir que le réseau d'octets occupe 855 m d'espace, et 814 instances de com.ezlippi.memoryleak $ CustomClassloader sont créées, qui correspondent essentiellement à la taille du tableau d'octets:
num #instances #bytes classe nom------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- com.ezlippi.memoryleak $ CustomClassloader 12: 820 53088 [ljava.util.hashTable $ Entrée; 15: 817 39216 java.util.hashTable 16: 915 36600 Java.lang.ref.softreference 17: 543 34752 java.net.url 18: 697 33456 Java.Nio.HeapCharbuffer 19: 817 32680 Java. java.util.treemap $ entrée 21: 928 29696 java.util.hashTable $ Entry 22: 1802 28832 java.util.hashset 23: 817 26144 java.security.codesource 24: 814 26048 Java.lang.threadlocal $ ThreadLocall
Metaspace Overflow
Afin de faire un débordement Metaspace, vous devez réduire un peu l'espace de Metaspace et charger suffisamment de classes avant le débordement du tas. Par conséquent, j'ai ajusté les paramètres JVM et ajusté la taille du réseau d'octets à 1kb, comme indiqué ci-dessous:
octet privé [] kb = nouveau octet [1024]; - xms100m -xmx100m -xx: + useParNewgc -xx: + USECOnCarksweepgc -xx: + printgcDetails -xx: + printheapatgc -xx: + printclasshistogramme -xx: MetaspaceSize = 2m -xx: maxMETRAPSE = 2MepSe
À partir du journal GC, nous pouvons voir que lorsque le Meraspace atteint le seuil GC (c'est-à-dire que la taille de la configuration maxmetaspaceSize) sera déclenchée:
java.lang.outofMemoryError: Metaspace << Aucune trace de pile disponible >> {Heap Before GC Invocations = 20 (complète 20): par Nouvelle génération Total 30720K, Utilisé 0k [0x000000000F9C000000, 0x000000000000000f9000) 0x00000000f9c00000, 0x0000000f9c00000, 0x0000000f9c00000, 0x0000000fb6b0000) de l'espace 3392k, 0% utilisé [0x0000000000000000000000) pour l'espace 3392k, 0% utilisé. [0x000000000000FBA00000, 0x000000000FBD500000) à l'espace 3392k, 0% utilisé [0x0000000000FBA00000, 0x000000010000000000) Metaspace utilisé 1806K, capacité 1988K, engagé 2048K, réservé 1056768K Espace utilisé 202K, Capacité 384K, Coried 384K 1048576K [GC complet (seuil GC de métadonnées) [CMSProcess terminé avec le code de sortie 1D'après l'exemple ci-dessus, nous pouvons voir que si le chargeur de classe et le threadlocal sont mal utilisés, cela conduira en effet à une fuite de mémoire. Le code source complet est en github