Caso y análisis
Antecedentes de problemas
En Tomcat, el siguiente código está dentro de la aplicación web, lo que hará que WebappClassLoader se filtre y no se pueda reciclar.
clase pública mycounter {private int count = 0; public void increment () {count ++; } public int getCount () {return Count; }} La clase pública myThreadLocal extiende ThreadLocal <Scounter> {} public class FlokingServlet extiende httpservlet {private static mythreadlocal mythreadLocal = new MyThreadLocal (); Doget vacío protegido (httpservletRequest, respuesta httpservletResponse) arroja servletException, ioexception {mycounter contador = mythreadlocal.get (); if (Counter == NULL) {contador = nuevo mycounter (); myThreadLocal.set (contador); } respuesta.getWriter (). println ("El hilo actual servido este servlet" + contar.getCount () + "Times"); Counter.Increment (); }} En el código anterior, siempre y cuando se llame LeakingServlet una vez y el hilo que lo ejecuta no se detiene, se producirá WebappClassLoader . Cada vez que vuelva reload la aplicación, tendrá una instancia adicional WebappClassLoader , que eventualmente conducirá a PermGen OutOfMemoryException .
Resolver el problema
Ahora pensemos en ello: ¿por qué ThreadLocal anterior causa fugas de memoria?
WebAppClassLoader
En primer lugar, ¿necesitamos averiguar qué demonios es WebappClassLoader ?
Para aplicaciones web que se ejecutan en contenedores Java EE, la implementación de cargadores de clase es diferente de la de las aplicaciones generales de Java. También se implementarán diferentes contenedores web de manera diferente. En Apache Tomcat, cada aplicación web tiene una instancia de cargador de clase correspondiente. Este cargador de clase también usa el modo proxy, la diferencia es que primero intenta cargar una determinada clase y luego proxy al cargador de clase principal si no se puede encontrar. Este es lo opuesto al orden de los cargadores de clase general. Esta es una práctica recomendada en la especificación Java Servlet, y su propósito es hacer que las clases de la aplicación web prioricen más altas que las proporcionadas por el contenedor web. Una excepción a este patrón de proxy es que las clases en la biblioteca Java Core no están dentro del alcance de la búsqueda. Esto también es para garantizar el tipo de seguridad de la Biblioteca Core Java.
En otras palabras, WebappClassLoader es un cargador de clase personalizado para que Tomcat cargue webapps. El cargador de clase de cada aplicación web es diferente. Esto es para aislar las clases cargadas por diferentes aplicaciones.
Entonces, ¿qué tienen que ver las características de WebappClassLoader con las filtraciones de memoria? Todavía no es visible, pero es una característica muy importante que merece nuestra atención: cada SebApp tiene su propio WebappClassLoader , que es diferente del cargador de clase Java Core.
Sabemos que WebappClassLoader debe deberse a que está fuertemente referenciada por otros objetos, por lo que podemos tratar de dibujar su diagrama de relación de referencia. ¡etc! ¿Qué funciona exactamente un cargador de clase? ¿Por qué se ve obligado a citar?
Ciclo de vida de clase y cargador de clase
Para resolver el problema anterior, tenemos que estudiar la relación entre el ciclo de vida de la clase y el cargador de clase.
Lo principal relacionado con nuestro caso es la desinstalación de la clase:
Después de utilizar la clase, si se cumple la siguiente situación, la clase se desinstalará:
1. Todas las instancias de esta clase han sido recicladas, es decir, no existen casos de esta clase en el montón de Java.
2. ClassLoader que carga esta clase ha sido reciclado.
3. El objeto java.lang.Class correspondiente a esta clase no se hace referencia en ninguna parte, y no hay ningún método para acceder a la clase a través de la reflexión en cualquier lugar.
Si se cumplen las tres condiciones anteriores, el JVM desinstalará la clase durante la recolección de basura en el área del método. El proceso de desinstalación de la clase es realmente para borrar la información de la clase en el área del método, y todo el ciclo de vida de la clase Java finaliza.
Las clases cargadas por el cargador de clase que vienen con la máquina virtual Java nunca se desinstalarán durante el ciclo de vida de la máquina virtual. Los cargadores de clase que vienen con máquinas virtuales Java incluyen cargadores de clase raíz, cargadores de clase de extensión y cargadores de clase de sistema. La máquina virtual Java en sí siempre hace referencia a estos cargadores de clase, y estos cargadores de clase siempre se refieren a los objetos de clase de la clase que cargan, por lo que siempre se pueden acceder a estos objetos de clase.
Las clases cargadas por cargadores de clase definidos por el usuario se pueden descargar.
Tenga en cuenta la oración anterior. Si WebappClassLoader se filtra, significa que las clases que carga no se puede desinstalar, lo que explica por qué el código anterior causa PermGen OutOfMemoryException .
Mire la imagen a continuación en el punto clave
Podemos encontrar que el objeto de cargador de clase se asocia bidireccionalmente con el objeto de clase que carga. Esto significa que el objeto de clase puede ser el culpable de la referencia forzada a WebappClassLoader , lo que hace que se filtre.
Diagrama de relación de cita
Después de comprender la relación entre el cargador de clase y el ciclo de vida de una clase, podemos comenzar a dibujar un diagrama de relación de referencia. ( LeakingServlet.class y myThreadLocal en la figura no son rigurosos en el dibujo de referencia, principalmente para expresar que myThreadLocal es una variable de clase)
A continuación, analizamos la causa de WebappClassLoader Fuge en función de la figura anterior.
1. LeakingServlet contiene MyThreadLocal static , lo que resulta en el ciclo de vida de myThreadLocal siempre que el ciclo de vida de LeakingServlet . Esto significa que myThreadLocal no se reciclará, y las referencias débiles son inútiles, por lo que el hilo actual no puede borrar la fuerte referencia del contador a través de las medidas de protección de ThreadLocalMap .
2. Cadena de referencia fuerte: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , haciendo que WebappClassLoader se filtre.
Resumir
Las filtraciones de memoria son difíciles de detectar y a menudo son causadas por muchas razones. Threadlocal se ha convertido en un visitante frecuente para fugas de memoria debido a su ciclo de vida ligado a hilos, y un poco de descuido conducirá a un desastre. Este artículo es solo un análisis de un caso específico. Si puede usar esto para aplicarlo a otros casos, sería excelente. Espero que este artículo sea útil para todos.