Mecanismo de carga de clase JVM (6)

1. Clasificación y principio de funcionamiento del cargador de clase.

El cargador de clases (ClassLoader) es una parte importante de la máquina virtual Java (JVM), responsable de cargar el código de bytes de la clase en la memoria y convertirlo en una clase Java ejecutable. La tarea principal del cargador de clases es encontrar el archivo de bytecode según el nombre de la clase y cargarlo en la memoria, luego realizar operaciones como verificación, preparación y análisis, y finalmente generar una clase ejecutable.

Clasificación de los cargadores de clase:

  1. Bootstrap Class Loader (Bootstrap Class Loader): Encargado de cargar la librería central de la plataforma Java, ubicada en <JAVA_HOME>/jre/libel directorio . Es una parte de la propia máquina virtual, implementada por C++, no una clase de Java, y es principalmente responsable de cargar la biblioteca de clases principal, como las clases en java.langel paquete .

  2. Extension Class Loader (Extension Class Loader): Encargado de cargar la biblioteca de extensiones de la plataforma Java, ubicada en <JAVA_HOME>/jre/lib/extel directorio . Es un cargador de clases implementado por Java que carga archivos JAR en <JAVA_HOME>/jre/lib/extel directorio .

  3. Cargador de clases de aplicaciones (Application Class Loader): También conocido como System Class Loader (Cargador de clases del sistema), responsable de cargar la biblioteca de clases especificada en la ruta de clases de la aplicación (Classpath). Es un cargador de clases definido por el desarrollador y la implementación predeterminada del cargador de clases de Java.

  4. Cargador de clases definido por el usuario: además de los cargadores de clases integrados mencionados anteriormente, los desarrolladores también pueden personalizar los cargadores de clases, heredar de java.lang.ClassLoaderla clase e implementar su propia lógica de carga de clases.

        El principio de funcionamiento del cargador de clases: el cargador de clases utiliza el modelo de delegación principal (modelo de delegación principal) para cargar clases, es decir, cuando un cargador de clases recibe una solicitud para cargar una clase, primero delega la tarea de carga a la clase principal cargador, solo cuando el cargador de clases principal no puede cargar la clase, el cargador de clases actual lo cargará.

El flujo de trabajo específico es el siguiente:

  1. Cuando un cargador de clases recibe una solicitud para cargar una clase, primero verifica si la clase ya se ha cargado. Si ya se ha cargado, devuelve la clase cargada directamente.
  2. Si la clase no se ha cargado antes, la tarea de carga se delega al cargador de clases padre. El cargador de clases principal verifica si la clase ya se ha cargado de la misma manera.
  3. Si el cargador de clases principal tampoco puede cargar la clase, el cargador de clases actual intentará cargarlo por sí mismo. Primero encuentra el archivo de clase, lee el código de bytes y crea el objeto de Clase correspondiente de acuerdo con el código de bytes.
  4. Si el cargador de clases actual aún no puede cargar la clase, se generará una ClassNotFoundException.

        A través del modelo de delegación parental, se puede garantizar que la carga de clases es una relación jerárquica de arriba hacia abajo, asegurando la unicidad y consistencia de las clases. Esta relación jerárquica hace que diferentes cargadores de clases sean responsables de cargar diferentes clases, logrando el aislamiento y la seguridad de las clases.

2. Modelo de delegación de padres

        El modelo de delegación principal (Parent Delegation Model) es un mecanismo de carga de clases que define la relación jerárquica y el orden de carga entre los cargadores de clases. De acuerdo con este modelo, cuando un cargador de clases recibe una solicitud para cargar una clase, primero delega la tarea de carga a su cargador de clases padre, y solo cuando el cargador de clases padre no puede cargar la clase, lo hace el cargador de clases actual. .

        El objetivo principal del modelo de delegación principal es garantizar la unicidad y la coherencia de las clases. A través de este modelo, es posible evitar conflictos de clases con el mismo nombre en diferentes cargadores de clases y garantizar que las clases se carguen en una relación jerárquica de arriba hacia abajo.

        La ventaja del modelo de delegación parental es que garantiza la coherencia y la seguridad de la carga de clases. Puede evitar conflictos de clases con el mismo nombre y puede garantizar la seguridad de la biblioteca de clases principal, evitando que las clases definidas por el usuario reemplacen las clases en la biblioteca de clases principal.

Sin embargo, a veces necesitamos romper el modelo de delegación de padres, personalizar el cargador de clases y cargar clases específicas en él. Algunos ejemplos comunes incluyen:

  1. En algunos contenedores o marcos, es necesario cargar una versión específica de la biblioteca de clases en lugar de usar la versión en la biblioteca de clases del sistema. En este momento, puede personalizar el cargador de clases para dar prioridad a la carga de una versión específica de la biblioteca de clases.
  2. Al implementar la función de reemplazo en caliente del código (HotSwap), es necesario cargar dinámicamente la clase modificada en tiempo de ejecución. En este momento, puede personalizar el cargador de clases para cargar el archivo de clase actualizado.
  3. En algunos escenarios de demanda especial, puede personalizar el cargador de clases para implementar una lógica de carga específica, como cargar archivos de clases desde una base de datos o una red.

        Al personalizar el cargador de clases y romper el modelo de delegación principal, podemos lograr un método de carga de clases más flexible y satisfacer necesidades específicas. Sin embargo, debe tenerse en cuenta que romper el modelo de delegación parental puede generar incertidumbre y riesgos de seguridad en la carga de clases, por lo que debe usarse con precaución.

3. El proceso de carga de clases

        La carga de clases es el proceso de cargar un archivo de código de bytes de una clase en la memoria y convertirlo en una clase Java ejecutable. Los cargadores de clases son responsables de realizar las operaciones de carga de clases y existen ciertas relaciones y colaboraciones entre ellos.

El proceso de carga de clases suele incluir los siguientes pasos:

  1. Cargando: busque y lea el archivo de código de bytes de la clase, generalmente desde el sistema de archivos, el archivo JAR o la red. El resultado de la carga es generar una representación de código de bytes correspondiente en la memoria, generalmente en forma de matriz de bytes.

  2. Verificación: Verifique la exactitud y seguridad del código de bytes. Comprueba si el código de bytes cumple con la especificación JVM, contiene códigos de operación o estructuras ilegales, viola las restricciones de seguridad y más.

  3. Preparación: Asignar memoria y establecer valores iniciales predeterminados para las variables estáticas de la clase. Estas variables incluyen campos estáticos y constantes, a los que se les asigna espacio de memoria en esta etapa, pero aún no se les han asignado valores iniciales.

  4. Resolución: convertir referencias simbólicas en referencias directas. Una referencia simbólica se refiere al nombre simbólico de una clase, método, campo, etc. utilizado en el código de bytes, mientras que una referencia directa se refiere a un puntero o identificador en la memoria. El proceso de análisis resuelve las referencias simbólicas en referencias directas, lo que permite que la máquina virtual acceda directamente a las clases, métodos, campos, etc. correspondientes.

  5. Inicialización: inicialice la clase, incluida la realización de la asignación de variables estáticas y la inicialización de bloques de código estático. En esta etapa, las variables estáticas de la clase reciben valores iniciales reales y se ejecuta el bloque de código estático.

4. El principio de realización de carga dinámica y proxy dinámico.

        La carga dinámica y el proxy dinámico son tecnologías de uso común en Java y todas se implementan en función del mecanismo de reflexión.

  1. Carga dinámica (carga dinámica): la carga dinámica se refiere a la carga dinámica de clases o recursos según sea necesario cuando el programa se está ejecutando, en lugar de determinarlo en el momento de la compilación. A través de la carga dinámica, las clases específicas se pueden cargar de acuerdo con las condiciones en tiempo de ejecución para lograr la expansión dinámica y la flexibilidad de las clases.

        El principio de implementación de la carga dinámica depende principalmente de ClassLoaderla clase y sus subclases. Al personalizar el cargador de clases, puede especificar cómo y dónde cargar las clases, logrando así una carga dinámica. Los pasos generales son los siguientes:

  • Cree un cargador de clases personalizado y anule su findClassmétodo para implementar la lógica de carga de clases.
  • Al cargar una clase, determine si se requiere carga dinámica y, de ser así, llame defineClassal método para convertir el código de bytes de la clase en Classun objeto y devolver el objeto.
  • Cuando utilice una clase cargada dinámicamente en un programa, invoque su método o acceda a su propiedad a través de la reflexión.
  1. Proxy dinámico (Dynamic Proxy): Dynamic Proxy es un patrón de diseño que permite crear una clase de proxy en tiempo de ejecución para reemplazar la clase original con el fin de controlar y ampliar la clase original. Los proxies dinámicos se utilizan a menudo para implementar escenarios como AOP (programación orientada a aspectos).

El principio de implementación del proxy dinámico depende principalmente de las clases e interfaces del java.lang.reflectpaquete Java . Los pasos generales son los siguientes:ProxyInvocationHandler

  • Defina una interfaz y declare los métodos que deben implementar tanto la clase proxy como la clase original.
  • Cree una clase de controlador de proxy que implemente InvocationHandlerla interfaz , anulando su invokemétodo en el que define la lógica de mejora del método de clase original.
  • Cree un objeto de proxy a través Proxy.newProxyInstancedel método y pase el objeto del procesador de proxy y la interfaz para ser proxy.
  • Cuando se llama al método del objeto proxy, en realidad se llamará invokeal método , ejecutando así la lógica mejorada del método de clase original.

        El proxy dinámico reemplaza el objeto original a través del objeto proxy e intercepta la llamada al método del objeto original, para realizar el control y la extensión de la clase original. A través de la generación dinámica de objetos proxy y el procesamiento de procesadores invocadores, se realiza la función de proxy dinámico.

Supongo que te gusta

Origin blog.csdn.net/zz18532164242/article/details/130856563
Recomendado
Clasificación