Caso e análise
Background Problem
No Tomcat, o código a seguir está dentro do WebApp, que fará com que WebappClassLoader vaze e não possa ser reciclado.
classe pública MyCounter {private int conting = 0; public void increment () {count ++; } public int getCount () {Return Count; }} classe pública mythreadlocal estende ThreadLocal <Counter> {} classe pública LeakServlet estende httpServlet {mythreadLocal mythreadLocal estático privado = novo mythreadLocal (); Void protegido Doget (solicitação httpServletRequest, httpServletResponse resposta) lança servletexception, ioexception {MyCounter contador = mythreadlocal.get (); if (contador == null) {contador = new MyCounter (); mythreadlocal.set (contador); } Response.getWriter (). println ("O thread atual serviu este servlet" + contador.getCount () + "Times"); contador.increment (); }} No código acima, desde que LeakingServlet seja chamado uma vez e o thread que ele não for interrompido, WebappClassLoader ocorrerá. Toda vez que você reload o aplicativo, você terá uma instância adicional WebappClassLoader , que eventualmente levará ao PermGen OutOfMemoryException .
Resolva o problema
Agora, vamos pensar sobre isso: por que ThreadLocal acima causa vazamentos de memória?
WebAppClassLoader
Primeiro de tudo, precisamos descobrir o que diabos é WebappClassLoader ?
Para aplicativos da Web em execução em contêineres Java EE, a implementação dos carregadores de classe é diferente da dos aplicativos Java General. Diferentes contêineres da Web também serão implementados de maneira diferente. No Apache Tomcat, cada aplicativo da Web possui uma instância de carregador de classe correspondente. Esse carregador de classe também usa o modo proxy, a diferença é que primeiro tenta carregar uma determinada classe e depois proxy para o carregador de classe pai, se não puder ser encontrado. Este é o oposto da ordem dos carregadores de classe geral. Essa é uma prática recomendada na especificação Java Servlet, e seu objetivo é tornar as próprias classes do aplicativo da web priorizarem mais do que as fornecidas pelo contêiner da web. Uma exceção a esse padrão de proxy é que as classes na biblioteca principal Java não estão dentro do escopo da pesquisa. Isso também é para garantir o tipo de segurança da biblioteca principal Java.
Em outras palavras, WebappClassLoader é um carregador de classe personalizado para o TomCat carregar o WebApps. O carregador de classe de cada webApp é diferente. Isso é para isolar as classes carregadas por diferentes aplicativos.
Então, o que os recursos do WebappClassLoader tem a ver com vazamentos de memória? Ainda não está visível, mas é uma característica muito importante que merece nossa atenção: cada aplicativo da web tem seu próprio WebappClassLoader , que é diferente do carregador de classe Java Core.
Sabemos que WebappClassLoader deve ser porque é fortemente referenciado por outros objetos, para que possamos tentar desenhar seu diagrama de relacionamento de referência. etc! O que exatamente um carregador de classe funciona? Por que é forçado a citar?
Ciclo de vida da classe e carregador de classe
Para resolver o problema acima, precisamos estudar a relação entre o ciclo de vida da classe e o carregador de classe.
A principal coisa relacionada ao nosso caso é a desinstalação da classe:
Depois que a aula for usada, se a seguinte situação for atendida, a classe será desinstalada:
1. Todas as instâncias desta classe foram recicladas, ou seja, não existem instâncias dessa classe na pilha Java.
2. ClassLoader esta classe foi reciclado.
3. O objeto java.lang.Class correspondente a esta classe não é referenciado em nenhum lugar e não há método para acessar a classe através da reflexão em qualquer lugar.
Se todas as três condições acima forem atendidas, a JVM desinstalará a classe durante a coleta de lixo na área do método. O processo de desinstalação da classe é realmente limpar as informações da classe na área do método e todo o ciclo de vida da classe Java termina.
As classes carregadas pelo carregador de classe que acompanham a máquina virtual Java nunca serão desinstaladas durante o ciclo de vida da máquina virtual. Os carregadores de classe que acompanham as máquinas virtuais Java incluem carregadeiras de classe raiz, carregadeiras de classe de extensão e carregadeiras de classe do sistema. A própria máquina virtual Java sempre faz referência a esses carregadores de classe, e esses carregadores de classe sempre se referem aos objetos de classe da classe que carregam, para que esses objetos de classe sejam sempre acessíveis.
As classes carregadas por carregadores de classe definidas pelo usuário podem ser descarregadas.
Observe a frase acima. Se WebappClassLoader vazar, isso significa que as classes que carregam não podem ser desinstaladas, o que explica por que o código acima causa PermGen OutOfMemoryException .
Veja a foto abaixo no ponto -chave
Podemos descobrir que o objeto de carregador de classe está bidirecionalmente associado ao objeto de classe que ele carrega. Isso significa que o objeto de classe pode ser o culpado da referência forçada ao WebappClassLoader , fazendo com que ele vaze.
Diagrama de relacionamento de citações
Depois de entender a relação entre o carregador de classe e o ciclo de vida de uma classe, podemos começar a desenhar um diagrama de relacionamento de referência. myThreadLocal LeakingServlet.class myThreadLocal
Abaixo, analisamos a causa do vazamento WebappClassLoader com base no diagrama acima.
1. LeakingServlet mantém MyThreadLocal static , resultando no ciclo de vida do myThreadLocal , desde que o ciclo de vida LeakingServlet . Isso significa que myThreadLocal não será reciclado e as referências fracas são inúteis; portanto, o encadeamento atual não pode limpar a forte referência do contador através das medidas de proteção do ThreadLocalMap .
2. Cadeia de referência forte: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , fazendo com que WebappClassLoader vazasse.
Resumir
Os vazamentos de memória são difíceis de detectar e geralmente são causados por muitos motivos. O Threadlocal tornou -se um visitante frequente para vazamentos de memória devido ao seu ciclo de vida ligado a threads, e um pouco de descuido levará a um desastre. Este artigo é apenas uma análise de um caso específico. Se você puder usá -lo para aplicá -lo a outros casos, seria excelente. Espero que este artigo seja útil para todos.