Cargador de clases
Se utiliza para suministrar código de bytes compilado a la JVM, que generalmente se almacena en archivos con la extensión.class
, pero también puede obtenerse de otras fuentes, por ejemplo, descargarse de la red o generarse por la propia aplicación. De acuerdo con la especificación Java SE, para ejecutar el código en la JVM, debe completar tres pasos:
-
cargar código de bytes de recursos y crear una instancia de la clase
Class
Esto incluye buscar la clase solicitada entre las cargadas anteriormente, obtener el código de bytes para cargar y verificar su corrección, crear una instancia de la clase
Class
(para trabajar con ella en tiempo de ejecución) y cargar las clases principales. Si las clases e interfaces principales no se han cargado, entonces la clase en cuestión se considera no cargada. -
vinculante (o vinculante)
Según la especificación, esta etapa se divide en tres etapas más:
- Verificación , se verifica la exactitud del código de bytes recibido.
- Preparación , asignando RAM para campos estáticos e inicializándolos con valores predeterminados (en este caso, la inicialización explícita, si la hay, ya ocurre en la etapa de inicialización).
- Resolución , resolución de enlaces simbólicos de tipos, campos y métodos.
-
inicializando el objeto recibido
aquí, a diferencia de los párrafos anteriores, todo parece estar claro sobre lo que debe suceder. Por supuesto, sería interesante entender exactamente cómo sucede esto.
- La clase debe estar completamente cargada antes de vincularse.
- Una clase debe probarse y prepararse completamente antes de inicializarse.
- Los errores de resolución de enlaces ocurren durante la ejecución del programa, incluso si se detectaron en la etapa de enlace.
Tipos de cargadores de Java
Hay tres cargadores estándar en Java, cada uno de los cuales carga una clase desde una ubicación específica:-
Bootstrap es un cargador básico, también llamado Primordial ClassLoader.
carga clases JDK estándar desde el archivo rt.jar
-
Extension ClassLoader : cargador de extensiones.
carga clases de extensión, que se encuentran en el directorio jre/lib/ext de forma predeterminada, pero se pueden configurar mediante la propiedad del sistema java.ext.dirs
-
System ClassLoader : cargador del sistema.
carga clases de aplicación definidas en la variable de entorno CLASSPATH
Clase abstracta ClassLoader
Cada cargador, a excepción del base, es descendiente de la clase abstractajava.lang.ClassLoader
. Por ejemplo, la implementación del cargador de extensiones es la clase sun.misc.Launcher$ExtClassLoader
y el cargador del sistema es sun.misc.Launcher$AppClassLoader
. El cargador base es nativo y su implementación está incluida en la JVM. Cualquier clase que se extienda java.lang.ClassLoader
puede proporcionar su propia forma de cargar clases con blackjack y estas mismas. Para ello es necesario redefinir los métodos correspondientes, que por el momento sólo puedo considerar superficialmente, porque No entendí este problema en detalle. Aquí están:
package java.lang;
public abstract class ClassLoader {
public Class<?> loadClass(String name);
protected Class<?> loadClass(String name, boolean resolve);
protected final Class<?> findLoadedClass(String name);
public final ClassLoader getParent();
protected Class<?> findClass(String name);
protected final void resolveClass(Class<?> c);
}
loadClass(String name)
Uno de los pocos métodos públicos, que es el punto de entrada para cargar clases. Su implementación se reduce a llamar a otro método protegido loadClass(String name, boolean resolve)
, que debe anularse. Si observa el Javadoc de este método protegido, podrá comprender algo como lo siguiente: se proporcionan dos parámetros como entrada. Uno es el nombre binario de la clase (o el nombre de clase completo) que debe cargarse. El nombre de la clase se especifica con una lista de todos los paquetes. El segundo parámetro es una bandera que determina si se requiere resolución de enlace simbólico. De forma predeterminada es false , lo que significa que se utiliza la carga diferida de clases. Además, según la documentación, en la implementación predeterminada del método, se realiza una llamada findLoadedClass(String name)
que verifica si la clase ya se ha cargado previamente y, de ser así, devuelve una referencia a esta clase. De lo contrario, se llamará al método de carga de clases del cargador principal. Si ninguno de los cargadores pudo encontrar una clase cargada, cada uno de ellos, en orden inverso, intentará encontrar y cargar esa clase, anulando el archivo findClass(String name)
. Esto se discutirá con más detalle en el capítulo "Esquema de carga de clases". Y finalmente, por último pero no menos importante, una vez cargada la clase, dependiendo del indicador de resolución , se decidirá si se cargan las clases a través de enlaces simbólicos. Un claro ejemplo es que la etapa de Resolución se puede llamar durante la etapa de carga de clases. En consecuencia, al extender la clase ClassLoader
y anular sus métodos, el cargador personalizado puede implementar su propia lógica para entregar código de bytes a la máquina virtual. Java también admite el concepto de cargador de clases "actual". El cargador actual es el que cargó la clase que se está ejecutando actualmente. Cada clase sabe con qué cargador se cargó y usted puede obtener esta información llamando a su archivo String.class.getClassLoader()
. Para todas las clases de aplicaciones, el cargador "actual" suele ser el del sistema.
Tres principios de carga de clases
-
Delegación
La solicitud para cargar la clase se pasa al cargador principal y se intenta cargar la clase en sí solo si el cargador principal no pudo encontrar ni cargar la clase. Este enfoque le permite cargar clases con el cargador que está lo más cerca posible del cargador base. Esto logra la máxima visibilidad de clase. Cada cargador mantiene un registro de las clases que cargó y las coloca en su caché. El conjunto de estas clases se llama alcance.
-
Visibilidad
El cargador sólo ve "sus" clases y las clases "principales" y no tiene idea de las clases que fueron cargadas por su "secundario".
-
Unicidad
Una clase sólo se puede cargar una vez. El mecanismo de delegación garantiza que el cargador que inicia la carga de clases no sobrecargue una clase que se cargó previamente en la JVM.
Esquema de carga de clases
Cuando se produce una llamada para cargar una clase, esta clase se busca en el caché de clases ya cargadas del cargador actual. Si la clase deseada no se ha cargado antes, el principio de delegación transfiere el control al cargador principal, que se encuentra en un nivel superior en la jerarquía. El cargador principal también intenta encontrar la clase deseada en su caché. Si la clase ya se ha cargado y el cargador conoce su ubicación,Class
se devolverá un objeto de esa clase. De lo contrario, la búsqueda continuará hasta llegar al gestor de arranque base. Si el cargador base no tiene información sobre la clase requerida (es decir, aún no se ha cargado), se buscará el código de bytes de esta clase en la ubicación de las clases que el cargador dado conoce, y si la clase no puede se cargará, el control volverá al cargador secundario, que intentará cargar desde fuentes que conoce. Como se mencionó anteriormente, la ubicación de las clases para el cargador base es la biblioteca rt.jar, para el cargador de extensiones - el directorio con extensiones jre/lib/ext, para el sistema - CLASSPATH, para el usuario puede ser algo diferente . Por lo tanto, el progreso de la carga de clases va en la dirección opuesta: desde el cargador raíz hasta el actual. Cuando se encuentra el código de bytes de la clase, la clase se carga en la JVM y se obtiene una instancia del tipo Class
. Como puede ver fácilmente, el esquema de carga descrito es similar a la implementación del método anterior loadClass(String name)
. A continuación puede ver este diagrama en el diagrama.
Como conclusión
En los primeros pasos para aprender un idioma, no es necesario comprender cómo se cargan las clases en Java, pero conocer estos principios básicos le ayudará a evitar la desesperación al encontrar errores comoClassNotFoundException
o NoClassDefFoundError
. Bueno, o al menos entender aproximadamente cuál es la raíz del problema. Por lo tanto, se produce una excepción ClassNotFoundException
cuando una clase se carga dinámicamente durante la ejecución del programa, cuando los cargadores no pueden encontrar la clase requerida ni en el caché ni en la ruta de clase. Pero el error NoClassDefFoundError
es más crítico y ocurre cuando la clase requerida estaba disponible durante la compilación, pero no era visible durante la ejecución del programa. Esto puede suceder si el programa olvidó incluir la biblioteca que utiliza. Bueno, el hecho mismo de comprender los principios de la estructura de la herramienta que utiliza en su trabajo (no necesariamente una inmersión clara y detallada en sus profundidades) agrega cierta claridad a la comprensión de los procesos que ocurren dentro de este mecanismo, que, en a su vez, conduce a un uso seguro de esta herramienta.
GO TO FULL VERSION