java - Diferencia entre el cargador de clases de contexto del hilo y el cargador de clases normal
multithreading jvm (4)
Agregando a la respuesta de @David Roussel, las clases pueden ser cargadas por múltiples cargadores de clases.
Vamos a entender cómo funciona el cargador de clases .
Desde el blog de javin paul en javarevisited:
ClassLoader
sigue tres principios.
Principio de delegación
Una clase se carga en Java, cuando es necesario. Supongamos que tiene una clase específica de la aplicación llamada Abc.class, la primera solicitud de carga de esta clase vendrá a Application ClassLoader, que delegará a su Extension ClassLoader principal, que además delegará en el cargador de clases Primordial o Bootstrap.
Bootstrap ClassLoader es responsable de cargar archivos de clase JDK estándar de rt.jar y es el padre de todos los cargadores de clases en Java. Bootstrap class loader no tiene ningún padre.
La extensión ClassLoader delega la solicitud de carga de la clase a su padre, Bootstrap y, si no tiene éxito, carga el directorio de clase jre / lib / ext o cualquier otro directorio señalado por la propiedad del sistema java.ext.dirs
El cargador de clases de sistema o aplicación y es responsable de cargar clases específicas de la aplicación desde la variable de entorno CLASSPATH, la opción de línea de comando -classpath o -cp, atributo Class-Path del archivo Manifest dentro de JAR.
Application class loader es un hijo de Extension ClassLoader y se implementa mediante la clase
sun.misc.Launcher$AppClassLoader
.
NOTA: Excepto el cargador de clases Bootstrap , que se implementa en lenguaje nativo principalmente en C, todos los cargadores de clases Java se implementan utilizando java.lang.ClassLoader
.
Principio de visibilidad
De acuerdo con el principio de visibilidad, Child ClassLoader puede ver la clase cargada por Parent ClassLoader pero viceversa no es cierto.
Principio de singularidad
De acuerdo con este principio, una clase cargada por Parent no debe ser cargada por Child ClassLoader de nuevo
¿Cuál es la diferencia entre el cargador de clases de contexto de un hilo y un cargador de clases normal?
Es decir, si Thread.currentThread().getContextClassLoader()
y getClass().getClassLoader()
devuelven diferentes objetos del cargador de clases, ¿cuál se usará?
Cada clase usará su propio cargador de clases para cargar otras clases. Entonces, si ClassA.class
referencia a ClassB.class
entonces ClassB
debe estar en el classpath del classloader de ClassA
, o sus padres.
El cargador de clases de contexto de subprocesos es el cargador de clases actual para el subproceso actual. Se puede crear un objeto a partir de una clase en ClassLoaderC
y luego pasar a un subproceso que es propiedad de ClassLoaderD
. En este caso, el objeto debe usar Thread.currentThread().getContextClassLoader()
directamente si desea cargar recursos que no están disponibles en su propio cargador de clases.
Esto no responde a la pregunta original, pero como la pregunta está altamente clasificada y vinculada para cualquier consulta de ContextClassLoader
, creo que es importante responder a la pregunta relacionada de cuándo se debe usar el cargador de clases de contexto. Respuesta corta: ¡ nunca use el cargador de clases de contexto ! Pero getClass().getClassLoader()
en getClass().getClassLoader()
cuando tenga que llamar a un método al que le falta un parámetro ClassLoader
.
Cuando el código de una clase pide que se cargue otra clase, el cargador de clases correcto a usar es el mismo cargador de clases que la clase que llama (es decir, getClass().getClassLoader()
). Así es como funcionan las cosas el 99.9% del tiempo porque esto es lo que hace JVM la primera vez que construye una instancia de una nueva clase, invoca un método estático o accede a un campo estático.
Cuando desee crear una clase utilizando reflexión (como cuando deserializa o carga una clase con nombre configurable), la biblioteca que realiza la reflexión siempre debe preguntar a la aplicación qué cargador de clases debe utilizar, recibiendo el ClassLoader
como un parámetro de la aplicación. La aplicación (que conoce todas las clases que necesitan construirse) debería pasarla getClass().getClassLoader()
.
Cualquier otra forma de obtener un cargador de clases es incorrecta. Si una biblioteca usa hacks como Thread.getContextClassLoader()
, sun.misc.VM.latestUserDefinedLoader()
, o sun.reflect.Reflection.getCallerClass()
es un error causado por una deficiencia en la API. Básicamente, Thread.getContextClassLoader()
existe solo porque quien diseñó la API ObjectInputStream
olvidó aceptar el ClassLoader
como parámetro, y este error ha perseguido a la comunidad de Java hasta el día de hoy.
Dicho esto, muchas clases de JDK utilizan uno de los pocos hacks para adivinar qué cargador de clases usar. Algunos usan ContextClassLoader
(que falla cuando ejecutas diferentes aplicaciones en un grupo de subprocesos compartidos, o cuando dejas ContextClassLoader null
), algunos recorren la pila (que falla cuando el ContextClassLoader null
directo de la clase es una biblioteca), otros usan el cargador de clases del sistema (que está bien, siempre y cuando esté documentado para usar solo las clases en CLASSPATH
) o el cargador de clases de arranque, y algunos usan una combinación impredecible de las técnicas anteriores (lo que hace que las cosas sean más confusas). Esto ha resultado en mucho llanto y crujir de dientes.
Cuando utilice una API de este tipo, primero, intente encontrar una sobrecarga del método que acepte el cargador de clases como un parámetro . Si no hay un método razonable, intente configurar ContextClassLoader
antes de la llamada a la API (y restablézcala después):
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
// call some API that uses reflection without taking ClassLoader param
} finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
Hay un artículo en javaworld.com que explica la diferencia => ¿Qué ClassLoader debería usar?
(1)
Los cargadores de clases de contexto de subprocesos proporcionan una puerta trasera alrededor del esquema de delegación de carga de clases.
Tome JNDI, por ejemplo: sus agallas se implementan mediante clases de arranque en rt.jar (comenzando con J2SE 1.3), pero estas clases de núcleo JNDI pueden cargar proveedores de JNDI implementados por proveedores independientes y potencialmente implementados en la ruta de la aplicación. Este escenario requiere que un cargador de clases principal (el principal en este caso) cargue una clase visible para uno de sus cargadores de clases secundarios (el sistema, por ejemplo). La delegación normal de J2SE no funciona, y la solución consiste en hacer que las clases JNDI principales utilicen cargadores de contexto de subprocesos, por lo que efectivamente "hacen un túnel" a través de la jerarquía del cargador de clases en la dirección opuesta a la delegación adecuada.
(2) de la misma fuente:
Esta confusión probablemente se mantendrá con Java por algún tiempo. Tome cualquier API J2SE con carga dinámica de recursos de cualquier tipo e intente adivinar qué estrategia de carga utiliza. Aquí hay una muestra:
- JNDI usa cargadores de clases de contexto
- Class.getResource () y Class.forName () usan el actual classloader
- JAXP usa cargadores de clases de contexto (a partir de J2SE 1.4)
- java.util.ResourceBundle usa el cargador de clases actual de la persona que llama
- Los controladores de protocolo de URL especificados a través de la propiedad del sistema java.protocol.handler.pkgs se buscan solo en los cargadores de clases del sistema y de arranque
- La API de serialización de Java usa el cargador de clases actual de la persona que llama de forma predeterminada