¿Descargando clases en java?

174

Tengo un cargador de clases personalizado para que una aplicación de escritorio pueda comenzar a cargar dinámicamente clases desde un AppServer con el que necesito hablar. Lo hicimos ya que la cantidad de frascos necesarios para hacer esto es ridícula (si quisiéramos enviarlos). También tenemos problemas de versión si no cargamos las clases dinámicamente en tiempo de ejecución desde la biblioteca AppServer.

Ahora, me topé con un problema en el que necesito hablar con dos AppServers diferentes y descubrí que, dependiendo de las clases que cargue primero, podría romper mal ... ¿Hay alguna forma de forzar la descarga de la clase sin matar realmente la JVM?

Espero que esto tenga sentido

el_eduardo
fuente
¿Tienes un cargador de clases para cada jarra? ¿Cómo archiva los contenedores OSGI para descargar el cargador de clases? Parece que no hay API de descarga en la clase del cargador de clases?
hetaoblog

Respuestas:

190

La única forma en que una clase se puede descargar es si el cargador de clases utilizado es recolección de basura. Esto significa que las referencias a cada clase y al cargador de clases en sí deben seguir el camino del dodo.

Una posible solución a su problema es tener un cargador de clases para cada archivo jar y un cargador de clases para cada uno de los servidores de aplicaciones que delegue la carga real de clases a cargadores de clases Jar específicos. De esa manera, puede apuntar a diferentes versiones del archivo jar para cada servidor de aplicaciones.

Sin embargo, esto no es trivial. La plataforma OSGi se esfuerza por hacer exactamente esto, ya que cada paquete tiene un cargador de clases diferente y las dependencias son resueltas por la plataforma. Tal vez una buena solución sería echarle un vistazo.

Si no desea utilizar OSGI, una posible implementación podría ser utilizar una instancia de la clase JarClassloader para cada archivo JAR.

Y cree una nueva clase MultiClassloader que amplíe Classloader. Esta clase internamente tendría una matriz (o Lista) de JarClassloaders, y en el método defineClass () iteraría a través de todos los cargadores de clase internos hasta que se pueda encontrar una definición, o se arroje una NoClassDefFoundException. Se pueden proporcionar un par de métodos de acceso para agregar nuevos JarClassloaders a la clase. Hay varias implementaciones posibles en la red para un MultiClassLoader, por lo que es posible que ni siquiera necesite escribir el suyo.

Si instancia un MultiClassloader para cada conexión al servidor, en principio es posible que cada servidor use una versión diferente de la misma clase.

Utilicé la idea de MultiClassloader en un proyecto, donde las clases que contenían scripts definidos por el usuario tenían que cargarse y descargarse de la memoria y funcionaban bastante bien.

Mario Ortegón
fuente
31
Tenga en cuenta también que según java.sun.com/docs/books/jls/second_edition/html/… la descarga de clases es una optimización y, dependiendo de la implementación de JVM, puede ocurrir o no.
55
Como una alternativa más fácil y liviana a OSGi, pruebe los módulos JBoss : carga de clases modularizada con cargador de clases por módulo (un grupo de tarros).
Ondra Žižka
42

Sí, hay formas de cargar clases y "descargarlas" más adelante. El truco consiste en implementar su propio cargador de clases que reside entre el cargador de clases de alto nivel (el cargador de clases del sistema) y los cargadores de clases de los servidores de aplicaciones, y esperar que los cargadores de clases del servidor de aplicaciones deleguen la carga de clases en los cargadores superiores .

Una clase se define por su paquete, su nombre y el cargador de clases que cargó originalmente. Programe un cargador de clases "proxy", que es el primero que se carga al iniciar la JVM. Flujo de trabajo:

  • El programa se inicia y la clase "principal" real es cargada por este cargador de clases proxy.
  • Cada clase que luego se carga normalmente (es decir, no a través de otra implementación de cargador de clases que podría romper la jerarquía) se delegará a este cargador de clases.
  • Los delegados del cargador de clases proxy java.xy sun.xal cargador de clases del sistema (estos no deben cargarse a través de ningún otro cargador de clases que no sea ​​el cargador de clases del sistema).
  • Para cada clase que sea reemplazable, cree una instancia de un cargador de clases (que realmente carga la clase y no lo delega al cargador de clases padre) y cárguelo a través de esto.
  • Almacene el paquete / nombre de las clases como claves y el cargador de clases como valores en una estructura de datos (es decir, Hashmap).
  • Cada vez que el cargador de clases proxy recibe una solicitud de una clase que se cargó antes, devuelve la clase del cargador de clases almacenado anteriormente.
  • Debería ser suficiente ubicar la matriz de bytes de una clase por su cargador de clases (o "eliminar" el par clave / valor de su estructura de datos) y volver a cargar la clase en caso de que desee cambiarla.

Hecho justo allí no debería venir una ClassCastException o LinkageError, etc.

Para obtener más información sobre las jerarquías de cargadores de clases (sí, eso es exactamente lo que está implementando aquí; -) mire "Programación Java basada en servidor" de Ted Neward : ese libro me ayudó a implementar algo muy similar a lo que desea.

Georgi
fuente
3
No entiendo a las personas que marcaron -1 para esta respuesta sin dejar un comentario. Para mi se ve bien. Quizás tener uno ClassLoaderpor clase es demasiado, uno ClassLoaderpor JAR tiene sentido. ¿Podría ser más específico sobre cómo forzar la carga de clases en el esquema propuesto? Por ejemplo, ¿cómo puedo garantizar que las instancias de clases cargadas por ClassLoaderA no sean referidas por instancias cargadas por ClassLoaderB?
dma_k
@dma_k exactamente, la respuesta es buena, pero no toca los puntos clave que mencionaste.
2013
@Georgi ¿existe una implementación existente que podamos instaurar / reutilizar para esto?
Trineo el
1
Sería muy útil si pudiera proporcionar el código Java de muestra. Para ser precisos, estoy buscando Cómo descargar clases usando CustomClassLoader pero no tuve suerte.
Sriharsha grv
@ Sriharshag.rv ¿Has probado esto e implementado una muestra?
niaomingjian
17

Escribí un cargador de clases personalizado, desde el cual es posible descargar clases individuales sin GCing el cargador de clases. Jar Class Loader

Kamran
fuente
Funciona de maravilla :). ¿Hay algún método para descargar todos los archivos de clase de un archivo jar?
Ercksen
Lamentablemente no por el momento. Pero lo investigaremos. Puede ser en los próximos lanzamientos.
Kamran
Por cierto, encontré una pequeña solución. Si tiene uno JarClassLoaderpara cada archivo jar que cargó, puede getLoadedClasses()invocarlo, luego iterar sobre cada uno y descargarlo.
Ercksen
12

Los cargadores de clases pueden ser un problema complicado. Especialmente puede encontrarse con problemas si está utilizando múltiples cargadores de clases y no tiene sus interacciones definidas de manera clara y rigurosa. Creo que para poder realmente descargar una clase que vas a tener que eliminar todas las referencias a las clases (y sus instancias) que intentas descargar.

La mayoría de las personas que necesitan hacer este tipo de cosas terminan usando OSGi . OSGi es realmente potente y sorprendentemente ligero y fácil de usar,

Steve g
fuente
7

Puede descargar un ClassLoader pero no puede descargar clases específicas. Más específicamente, no puede descargar clases creadas en un ClassLoader que no está bajo su control.

Si es posible, sugiero usar su propio ClassLoader para que pueda descargar.

Jason Cohen
fuente
4

Las clases tienen una fuerte referencia implícita a su instancia de ClassLoader, y viceversa. Son basura recolectada como con los objetos Java. Sin presionar la interfaz de herramientas o similar, no puede eliminar clases individuales.

Como siempre, puede tener pérdidas de memoria. Cualquier referencia fuerte a una de sus clases o cargador de clases filtrará todo. Esto ocurre con las implementaciones Sun de ThreadLocal, java.sql.DriverManager y java.beans, por ejemplo.

Tom Hawtin - tackline
fuente
-1

Si está viendo en vivo si la clase de descarga funcionó en JConsole o algo así, intente también agregar java.lang.System.gc()al final de la lógica de descarga de su clase. Se activa explícitamente el recolector de basura.

Aleksander Drozd
fuente
3
Cuidado: System.gc () no necesariamente llama a GC. Solo le pide a jvm que lo inicie, pero no lo impone. E IME a menudo no inicia el GC: - \
Juh_