Descarregando classes em java?

174

Eu tenho um carregador de classes personalizado para que um aplicativo da área de trabalho possa iniciar dinamicamente o carregamento de classes de um AppServer com o qual preciso conversar. Fizemos isso porque a quantidade de frascos necessários para fazer isso é ridícula (se quisermos enviá-los). Também temos problemas de versão se não carregarmos as classes dinamicamente em tempo de execução da biblioteca AppServer.

Agora, acabei de encontrar um problema em que preciso conversar com dois AppServers diferentes e descobri que, dependendo de quais classes eu carrego primeiro, posso quebrar bastante ... Existe alguma maneira de forçar o descarregamento da classe sem realmente matar a JVM?

Espero que isso faça sentido

el_eduardo
fonte
Você tem um carregador de classe para cada jar? Como os contêineres OSGI arquivam para descarregar o classloader? Parece que não há API de descarregamento na classe classloader?
Hetaoblog 12/08/11

Respostas:

190

A única maneira de descarregar uma classe é se o Classloader usado for coletado de lixo. Isso significa que as referências a todas as classes e ao próprio carregador de classes precisam seguir o caminho do dodo.

Uma solução possível para o seu problema é ter um Classloader para cada arquivo jar e um Classloader para cada um dos AppServers que delega o carregamento real de classes para carregadores de classes Jar específicos. Dessa forma, você pode apontar para diferentes versões do arquivo jar para cada servidor de aplicativos.

Isso não é trivial, no entanto. A plataforma OSGi se esforça para fazer exatamente isso, pois cada pacote configurável possui um carregador de classe diferente e as dependências são resolvidas pela plataforma. Talvez uma boa solução seria dar uma olhada nisso.

Se você não deseja usar o OSGI, uma implementação possível pode ser usar uma instância da classe JarClassloader para cada arquivo JAR.

E crie uma nova classe MultiClassloader que estenda o Classloader. Essa classe internamente teria uma matriz (ou Lista) de JarClassloaders e, no método defineClass (), iteraria por todos os classloaders internos até que uma definição fosse encontrada ou uma NoClassDefFoundException fosse lançada. Alguns métodos de acessador podem ser fornecidos para adicionar novos JarClassloaders à classe. Existem várias implementações possíveis na rede para um MultiClassLoader, portanto você pode nem precisar escrever sua própria.

Se você instancia um MultiClassloader para todas as conexões com o servidor, em princípio é possível que cada servidor use uma versão diferente da mesma classe.

Usei a ideia do MultiClassloader em um projeto, onde as classes que continham scripts definidos pelo usuário tinham que ser carregadas e descarregadas da memória e funcionavam muito bem.

Mario Ortegón
fonte
31
Observe também que, de acordo com java.sun.com/docs/books/jls/second_edition/html/… o descarregamento de classes é uma otimização e, dependendo da implementação da JVM, pode ou não ocorrer.
5
Como uma alternativa mais fácil e leve ao OSGi, tente o JBoss Modules - carregamento de classe modularizado com carregador de classe por módulo (um grupo de jarros).
Ondra Žižka
42

Sim, existem maneiras de carregar classes e "descarregá-las" posteriormente. O truque é implementar seu próprio carregador de classes, que reside entre o carregador de classes de alto nível (o carregador de classes do sistema) e os carregadores de classe dos servidores de aplicativos, e esperar que os carregadores de classes do servidor de aplicativos delegem o carregamento de classe nos carregadores superiores .

Uma classe é definida por seu pacote, seu nome e o carregador de classes que ele carregou originalmente. Programe um carregador de classes "proxy", que é o primeiro carregado ao iniciar a JVM. Fluxo de trabalho:

  • O programa inicia e a classe "principal" real é carregada por este carregador de classes proxy.
  • Todas as classes que são normalmente carregadas (ou seja, não através de outra implementação do carregador de classes que possa quebrar a hierarquia) serão delegadas a esse carregador de classes.
  • O carregador de classes proxy delega java.xe sun.xao carregador de classes do sistema (eles não devem ser carregados através de nenhum outro carregador de classes que não seja o carregador de classes do sistema).
  • Para todas as classes substituíveis, instancie um carregador de classes (que realmente carrega a classe e não a delega ao carregador de classes pai) e carregue-o com isso.
  • Armazene o pacote / nome das classes como chaves e o carregador de classes como valores em uma estrutura de dados (por exemplo, Hashmap).
  • Toda vez que o carregador de classes proxy obtém uma solicitação para uma classe que foi carregada antes, ele retorna a classe do carregador de classes armazenado anteriormente.
  • Deve ser o suficiente para localizar a matriz de bytes de uma classe pelo seu carregador de classes (ou "excluir" o par de chave / valor da sua estrutura de dados) e recarregar a classe, caso você queira alterá-la.

Feito ali, não deve vir um ClassCastException ou LinkageError etc.

Para obter mais informações sobre hierarquias do carregador de classes (sim, é exatamente isso que você está implementando aqui; -) veja "Programação Java baseada em servidor" de Ted Neward - esse livro me ajudou a implementar algo muito semelhante ao que você deseja.

Georgi
fonte
3
Não entendo as pessoas que marcaram -1 para esta resposta sem deixar um comentário. Para mim parece bom. Talvez tendo umClassLoader por classe seja um pouco demais, um ClassLoaderpor JAR faz sentido. Poderia ser mais específico sobre como forçar o upload de classe no esquema proposto? Por exemplo, como posso garantir que instâncias de classes carregadas por ClassLoaderA não são referidas por instâncias carregadas por ClassLoaderB?
dma_k
@dma_k exatamente, a resposta é boa, mas não está tocando nos pontos principais que você mencionou.
zinking 21/10
@Georgi: Existe uma implementação existente que podemos instanear / reutilizar para isso?
Sled
1
Seria muito muito útil, se você pudesse fornecer o código java de exemplo. Para ser mais preciso, estou procurando Como descarregar classes usando o CustomClassLoader, mas não tive sorte.
Sriharsha grv
@ Sriharshag.rv Você já tentou isso e implementou uma amostra?
Niaomingjian
17

Eu escrevi um carregador de classe personalizado, a partir do qual é possível descarregar classes individuais sem GCing o carregador de classe. Carregador de Classe Jar

Kamran
fonte
Funciona como um encanto :). Existe algum método para descarregar todos os arquivos de classe de um arquivo jar?
Ercksen 13/10/2015
Infelizmente não no momento. Mas analisarei isso. Pode ser nos lançamentos futuros.
30515 Kamram
A propósito, eu encontrei um pouco de solução alternativa. Se você tiver um JarClassLoaderpara cada arquivo jar que você carregou, poderá chamá getLoadedClasses()-lo, iterar sobre cada um e descarregá-lo.
Ercksen
12

Os carregadores de classes podem ser um problema complicado. Você pode ter problemas especialmente se estiver usando vários carregadores de classe e não tiver suas interações definidas de forma clara e rigorosa. Eu acho que, para realmente poder descarregar uma classe, você precisará remover todas as referências a quaisquer classes (e suas instâncias) que você está tentando descarregar.

A maioria das pessoas que precisam fazer esse tipo de coisa acaba usando o OSGi . O OSGi é realmente poderoso, surpreendentemente leve e fácil de usar,

Steve g
fonte
7

Você pode descarregar um ClassLoader, mas não pode descarregar classes específicas. Mais especificamente, você não pode descarregar classes criadas em um ClassLoader que não está sob seu controle.

Se possível, sugiro usar seu próprio ClassLoader para que você possa descarregar.

Jason Cohen
fonte
4

As classes têm uma referência forte implícita à instância do ClassLoader e vice-versa. São lixo coletado como nos objetos Java. Sem acessar a interface das ferramentas ou similar, você não pode remover classes individuais.

Como sempre, você pode obter vazamentos de memória. Qualquer referência forte a uma de suas classes ou carregador de classes vazará a coisa toda. Isso ocorre com as implementações da Sun de ThreadLocal, java.sql.DriverManager e java.beans, por exemplo.

Tom Hawtin - linha de orientação
fonte
-1

Se você estiver assistindo ao vivo, se a classe de descarregamento funcionou no JConsole ou algo assim, tente adicionar java.lang.System.gc()no final da lógica de descarregamento de classe. Ele dispara explicitamente o Garbage Collector.

Aleksander Drozd
fonte
3
Cuidado: System.gc () não precisa chamar o GC. Ele apenas pede à jvm para iniciá-lo, mas não o impõe. E o IME geralmente não inicia o GC: - \
Juh_