Memory leak no VRaptor 2.6.2

Após dar “start” e “stop” algumas vezes em uma aplicação usando o VRaptor 2.6.2, no Tomcat 7.0.14 (num Ubuntu 10.10 32-bit, com a JVM da Sun, versão 1.6.0 update 24), recebo um OutOfMemoryError, reclamando que não há espaço no PermGen.

Na saída do Tomcat, parece que o culpado é uma classe do VRaptor, o org.vraptor.url.DefaultViewLocator:

Sep 7, 2011 2:16:24 AM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
SEVERE: The web application [] created a ThreadLocal with key of type [org.picocontainer.DefaultPicoContainer.IntoThreadLocal] (value [org.picocontainer.DefaultPicoContainer$IntoThreadLocal@6dd138]) and a value of type [java.lang.Class] (value [class org.vraptor.url.DefaultViewLocator]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak. 

(repetido diversas vezes)

Ligando um profiler na aplicação (YourKit 9.5.6, fazendo profiling remoto, sendo que a JVM foi inicializada com o agent instalado), após um ciclo start/stop no Tomcat, vejo que sobra uma instância do WebappClassLoader do Tomcat, que não morre mesmo após inúmeros Full GC.

Com o profiler, dá pra ver que há dois caminhos somente com strong references que levam ao WebappClassLoader, e eles são:

Thread(“http-bio-8080-exec-14”).threadLocals.table[0].value ==> DefaultViewLocator (cujo ClassLoader é WebappClassLoader)
Thread(“http-bio-8080-exec-14”).threadLocals.table[0].value ==> Protection domain of DefaultViewLocator (cujo ClassLoader é WebappClassLoader)

Parece então que o VRaptor registra o DefaultViewLocator como ThreadLocal em algum dos worker threads (que atendem requisições http) do Tomcat, e esquece de tirar.

Pessoal da Caelum, vcs confirmam esse bug? Já foi corrigido? Tem planos de corrigir?

Pesquisei bastante no Google e não encontrei em lugar nenhum o código fonte do VRaptor 2.6.2. O mais próximo que encontrei foi um SVN aparentemente abandonado no qual o pom.xml declara a versão 2.6.1-SNAPSHOT. Encontrei outro SVN com a versão 2.6.0. No site de vocês não tem absolutamente nada sobre o VRaptor 2.

Aguardo resposta,

Bruno

olá bfreis,

o VRaptor está na versão 3.3.1, não estamos mais desenvolvendo a 2.6

alguma chance de migrar pra versão nova?

Isso não é um problema do VRaptor, mas sim do Tomcat. É bem conhecido, e antigo também, os problemas de memory leak quando você faz stop/start da aplicação. O correto é você fazer um stop/start no servidor completo, e não apenas na aplicação.

Dê uma pesquisada no Google que você verá várias explicações sobre isso.

[quote=Lucas Cavalcanti]olá bfreis,

o VRaptor está na versão 3.3.1, não estamos mais desenvolvendo a 2.6

alguma chance de migrar pra versão nova?[/quote]

Oi, Lucas.

Infelizmente, é impossível a migração. O projeto web de nosso sistema é grande demais para ser portado agora.

Teria como vcs disponibilizarem em algum lugar o código da versão 2.6.2?

Obrigado!

tenta ver o que o garcia-jj falou, pode ser um leak do Tomcat (para qqer coisa que vc usa thread local)

de qqer forma o source do VRaptor está aqui:

[quote=Lucas Cavalcanti]tenta ver o que o garcia-jj falou, pode ser um leak do Tomcat (para qqer coisa que vc usa thread local)

de qqer forma o source do VRaptor está aqui:
http://sourceforge.net/scm/?type=svn&group_id=158027[/quote]

Oi, Lucas.

Eu não uso ThreadLocal em lugar nenhum nesse projeto. Ele certamente foi intrudozido por alguma dependência, e se vc ler a trecho de log que mandei, a conclusão mais provável é que foi o VRaptor, no uso que faz do Pico Container. Aliás, esqueci de mencionar na questão original, já experimentei trocar a versão do Pico Container para a última release disponível, mas não adianta, o mesmo comportamento continua.

Nesse link, não encontro o código da release 2.6.2. A mais release recente disponível é 2.6.0. No SVN, o trunk tem a versão 2.6.1-SNAPSHOT. Nos tags/branches não tem nada melhor. Já tinha tropeçado nesse repositório com minhas buscas iniciais.

Bruno

vc não, mas o VRaptor sim…

derrubar o servidor e subir de novo não resolve o seu problema?

[quote=garcia-jj]Isso não é um problema do VRaptor, mas sim do Tomcat. É bem conhecido, e antigo também, os problemas de memory leak quando você faz stop/start da aplicação. O correto é você fazer um stop/start no servidor completo, e não apenas na aplicação.

Dê uma pesquisada no Google que você verá várias explicações sobre isso.[/quote]

Garcia,

O Tomcat possui um monte de proteções de memory leak. Se vc der uma olhada no código, verá que o software é bastante maduro nessa área. Ele até consegue, sozinho, se recuperar de alguns dos problemas que causariam leaks de memória (ex: dependendo do caso, ele consegue, por conta própria, matar os threads de thread pools que vc abriu e não se preocupou em fechar no shutdown, etc).

Se vc escrever uma aplicação web super simples e bem comportada (isto é, que limpa todos os recursos utilizados, mata os threads que lançou, não deixa sobrar nenhum tipo de cache acessível globalmente, etc), vc pode verificar com um profiler que após um ciclo start/stop o WebappClassLoader que carregou sua aplicação será corretamente jogado fora.

Bruno

Exato. Por isso que imaginei que o problema estaria no código do VRaptor (e por isso estou buscando esse código, na versão 2.6.2, pra investigar e corrigir o problema).

Derrubar e subir o servidor não resolve o problema. Apenas contorna.

Hmm. Fiz uma rápida pesquisa aqui e parece que foi introduzido uma série de proteções no Tomcat 7. Bom, felizmente não uso mais Tomcat desde os tempos do 4, e naquele tempo tinhamos muitos problemas com isso, principalmente quanto a atributos estáticos de classes, deregister de drivers JDBC e outros.

Aqui tem um link com algumas informações sobre os memory leaks: https://wiki.apache.org/tomcat/MemoryLeakProtection

Eu não sei muito o que dizer sobre esse problema porque usei o VRaptor 2.6 muito pouco. Talvez seja interessante pensar em usar o VRaptor 3 em modo compatibilidade com o 2.6, se realmente o problema é no VRaptor.

Será que no Jetty temos o mesmo problema?

Pessoal,

Esse problema, como falaram, é velho conhecido. É causado diretamente pela arquitetura de ClassLoaders da JVM, e o coitado do Tomcat não tem nada a ver com isso. Muito pelo contrário, o Tomcat, como falaram, tem diversas proteções contra memory leaks famosos, tentando minimizar o problema.

Pra quem tem interesse em saber como funciona: o servidor cria um WebAppClassLoader para cada aplicação deployada. Por definição, todo ClassLoader Java possui uma referência para todas as classes daquele ClassLoader (e toda classe possui uma referência para o ClassLoader que a carregou). Quando você faz hot deploy, no cenário ideal, o servidor libera todas as referências para o WebAppClassLoader anterior, permitindo que as classes da aplicação antiga sejam coletadas. Na prática, isso é bem difícil de acontecer porque sempre algum framework ou API vai esquecer uma referência para alguma classe do ClassLoader, segurando a aplicação toda na memória.

O memory leak acontece sempre que algum ClassLoader diferente do WebAppClassLoader segura alguma referência para qualquer classe ou objeto da sua aplicação. E não é difícil achar zilhões de casos. O mais básico seria o container dando new na sua Servlet e, portanto, segurando uma referência para ela (e óbvio que o Tomcat se precavê contra isso). Um exemplo clássico são Drivers JDBC que geralmente são colocados na aplicação (WEB-INF/lib) mas registrados no java.sql.DriverManager (bootstrap classloader). Você deveria desregistrar todo driver JDBC com DriverManager.deregisterDriver quando o contexto desliga; pouca gente faz isso, então o listener do Spring, por exemplo, faz por você, assim como o Tomcat em suas versões mais recentes (mas repare, não deveria ser trabalho deles, é a aplicação que está errada).

O Tomcat tenta resolver vários memory leaks famosos: http://wiki.apache.org/tomcat/MemoryLeakProtection
Mas há zilhões de outras formas de você causar um leak: https://gist.github.com/1172716

Agora falando do problema específico do tópico: realmente o VRaptor está errado em largar referências ThreadLocal, é um leak e o culpado é o VRaptor. Mas o Tomcat mais novo já consegue se recuperar disso! A própria mensagem no log que você mandou indica que esse leak foi evitado (o Tomcat joga fora todas as threads antigas para eliminar os ThreadLocals, e renova seu pool interno de Threads). Ou seja, muito possivelmente, você está caindo em algum outro memory leak, não esse especificamente (embora, em versões mais antigas do Tomcat, esse leak do VRaptor seria perigoso).

EDITADO: Reparei agora que o 1o post já identifica no profiler que o ThreadLocal está realmente perdido lá, culpa do VRaptor. E, no caso, trata-se de um objeto da aplicação esquecido lá, coisa que o Tomcat não consegue limpar sozinho (só limpa ThreadLocals criados pela aplicação).

Como arrumar então? Por experiência prática própria, encontrar esses leaks todos é quase impossível. Uma aplicação de tamanho razoável usa zilhões de frameworks e o leak pode estar escondido em qualquer um deles. A solução? Jamais fazer hot deploy em produção, sempre pare o servidor e suba uma instância nova com a nova aplicação.

Abraços

É uma pena que não tem um botão like aqui. :slight_smile:

Parece que finalmente estamos todos de acordo que este problema tem mais cara de ser culpa do VRaptor que do Tomcat.

Sendo assim, insisto: onde posso conseguir o código fonte da versão 2.6.2 do VRaptor? Este código foi perdido?

Obrigado!

Bruno

Não seria esse aqui? https://vraptor2.svn.sourceforge.net/svnroot/vraptor2

Não.

Esse repositório tem a versão 2.6.1-SNAPSHOT. Estou à procura do source da versão 2.6.2 final.

bfreis,

a última versão de verdade do VRaptor 2 foi a 2.6.0.
A versão 2.6.2 foi uma adaptação do VRaptor 2 para conseguir rodar junto com o VRaptor 3 (note que não existe a versão 2.6.1, e que a data do 2.6.2 (http://repo1.maven.org/maven2/org/vraptor/vraptor/2.6.2/) coincide com a data do lançamento do vraptor 3.0.0 (http://code.google.com/p/vraptor3/downloads/detail?name=vraptor-3.0.0.zip&can=4&q=#makechanges) )

as alterações foram bem bestas, se eu não me engano (algumas classes que eram default package ficando públicas, ou coisas do tipo)

o ThreadLocal de que o log está falando não está dentro do VRaptor, está dentro do próprio pico:

http://grepcode.com/file/repo1.maven.org/maven2/org.picocontainer/picocontainer/2.13.6/org/picocontainer/DefaultPicoContainer.java#DefaultPicoContainer.IntoThreadLocal

a única coisa que o VRaptor faz de diferente é guardar uma referência para um DefaultPicoContainer:
http://vraptor2.svn.sourceforge.net/viewvc/vraptor2/trunk/core/src/main/java/org/vraptor/plugin/pico/PicoContainerPlugin.java?revision=201&view=markup

talvez tentar limpar essa instância que está no ApplicationContext resolva o seu problema… talvez vc nem precise mexer no source do VRaptor

Ele não vem junto com o vraptor3?

em lib/optional/vraptor2

Ops… desculpe, não vi a última mensagem do Lucas.