¿Entiende el mecanismo de carga de clases de JVM que se debe preguntar en las entrevistas?

prefacio

Esta vez, se trae otra parte importante de la JVM, el mecanismo de carga de clases, sin tonterías, directamente abierto.

texto

1. El proceso de carga de clases.

El ciclo de vida completo de una clase comienza con la carga en la memoria de la máquina virtual y la descarga de la memoria, y su ciclo de vida completo incluye siete etapas: carga, verificación, preparación, análisis, inicialización, uso y descarga. Entre ellos, las tres partes de verificación, preparación y análisis se denominan colectivamente conexión.

1) Carga

Una etapa del proceso de "carga de clases". Durante la etapa de carga, la máquina virtual debe hacer 3 cosas:

  • Obtenga el flujo de bytes binarios que define esta clase por su nombre completo.
  • Convierta la estructura de almacenamiento estática representada por este flujo de bytes en la estructura de datos de tiempo de ejecución del área de método.
  • Un objeto java.lang.Class que representa esta clase se genera en la memoria como una entrada de acceso para varios datos de esta clase en el área de métodos.

2) Verificar

El primer paso de la fase de conexión, el propósito de esta fase es garantizar que la información contenida en el flujo de bytes del archivo Class cumpla con los requisitos de la máquina virtual actual y no comprometa la seguridad de la propia máquina virtual. En general, la etapa de verificación generalmente completará las siguientes cuatro etapas de acciones de verificación: verificación de formato de archivo, verificación de metadatos, verificación de código de bytes y verificación de referencia de símbolo.

3) Preparar

Esta etapa es la etapa de asignación formal de memoria para las variables de clase (variables estáticas modificadas) y establecimiento de los valores iniciales de las variables de clase. La memoria utilizada por estas variables se asignará en el área de método. El valor inicial mencionado aquí es "generalmente" el valor cero del tipo de datos. La siguiente tabla enumera el valor cero de todos los tipos de datos básicos en Java.

4) analizar

Esta fase es el proceso por el cual la máquina virtual reemplaza las referencias simbólicas en el grupo constante con referencias directas. La acción de análisis se realiza principalmente para siete tipos de referencias simbólicas, a saber, clases o interfaces, campos, métodos de clase, métodos de interfaz, tipos de métodos, identificadores de métodos y calificadores de sitios de llamadas.

5) Inicializar

En la etapa de inicialización, se ejecuta realmente el código del programa Java definido en la clase. En la etapa de preparación se le ha asignado a la variable el valor cero inicial requerido por el sistema, y ​​en la etapa de inicialización se inicializarán las variables de clase y demás recursos de acuerdo al plan subjetivo realizado por el programador a través del programa.

También podemos expresarlo de otra forma más directa: la fase de inicialización es el proceso de ejecutar el método constructor de clases <clinit>(). <clinit>() no es un método que los programadores escriben directamente en código Java, sino que el compilador Javac lo genera automáticamente.

El compilador genera el método <clinit>(), recopila automáticamente las asignaciones de todas las variables de clase en la clase y fusiona las declaraciones en el bloque de declaraciones estáticas (bloque {} estático). El orden de la colección del compilador está determinado por la declaración en el archivo fuente Dependiendo del orden de aparición, solo se puede acceder a las variables definidas antes del bloque de declaraciones estáticas en el bloque de declaraciones estáticas, y las variables definidas después se pueden asignar valores en el bloque de declaraciones estáticas anterior, pero no ser accedido.

También escribí una pregunta de entrevista sobre la inicialización antes: una interesante pregunta de entrevista de "inicialización" , y los estudiantes interesados ​​​​pueden echar un vistazo. 

2. ¿Qué cargadores de clases hay en la máquina virtual Java?

Desde la perspectiva de la máquina virtual Java, solo hay dos cargadores de clases diferentes:

Uno es Bootstrap ClassLoader, que está implementado en C++ y es parte de la propia máquina virtual;

El otro son todos los demás cargadores de clases, que se implementan mediante el lenguaje Java, independientemente del exterior de la máquina virtual, y todos heredan de la clase abstracta java.lang.ClassLoader.

Desde la perspectiva de los desarrolladores de Java, la mayoría de los programas de Java utilizan los cargadores de clases proporcionados por los siguientes tres sistemas.

1) Inicie el cargador de clases (Bootstrap ClassLoader):

Este cargador de clases es responsable de almacenar los archivos almacenados en el directorio <JAVA_HOME>\lib, o en la ruta especificada por el parámetro -Xbootclasspath, y reconocido por la máquina virtual (solo reconocido por el nombre del archivo, como rt.jar, el nombre no coincide La biblioteca de clases no se carga incluso si se coloca en el directorio lib) La biblioteca de clases se carga en la memoria de la máquina virtual.

2) Extensión ClassLoader (Extensión ClassLoader):

Este cargador está implementado por sun.misc.Launcher$ExtClassLoader, que es responsable de cargar todas las bibliotecas de clases en el directorio <JAVA_HOME>\lib\ext o en la ruta especificada por la variable de sistema java.ext.dirs. Los desarrolladores pueden usar directamente cargador de clases de extensión.

3) Cargador de clases de aplicaciones (ClassLoader de aplicaciones):

Sun.misc.Launcher$AppClassLoader implementa este cargador de clases. Dado que este cargador de clases es el valor de retorno del método getSystemClassLoader() en ClassLoader, generalmente se le llama cargador de clases del sistema. Es responsable de cargar la biblioteca de clases especificada en el classpath del usuario (ClassPath). Los desarrolladores pueden usar este cargador de clases directamente. Si la aplicación no ha personalizado su propio cargador de clases, en general, esta es la clase predeterminada en el programa. Loader.

Nuestras aplicaciones son cargadas por estos tres cargadores de clases.Si es necesario, podemos agregar nuestros propios cargadores de clases definidos. La relación entre estos cargadores de clases es generalmente como se muestra.

3. ¿Qué es el modelo de delegación parental?

Si un cargador de clases recibe una solicitud de carga de clases, no intentará cargar la clase primero, sino que delegará la solicitud al cargador de clases principal para que la complete. Este es el caso para todos los niveles de cargadores de clases, por lo que todas las solicitudes de carga deben eventualmente se pasará al cargador de clases de inicio de nivel superior, y solo cuando el cargador principal informe que no puede completar la solicitud de carga (la clase requerida no se encuentra en su ámbito de búsqueda), el cargador secundario intentará cargar.

El código fuente para la carga de clases es el siguiente:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 1、检查请求的类是否已经被加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 2、将类加载请求先委托给父类加载器
                    if (parent != null) {
                        // 父类加载器不为空时,委托给父类加载进行加载
                        c = parent.loadClass(name, false);
                    } else {
                        // 父类加载器为空,则代表当前是Bootstrap,从Bootstrap中加载类
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器抛出ClassNotFoundException
                    // 说明父类加载器无法完成加载请求
                }

                if (c == null) {
                    // 3、在父类加载器无法加载的时候,再调用本身的findClass方法来进行类加载
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

4. ¿Por qué utilizar el modelo de delegación parental?

1) Al usar el modelo de delegación de padres para organizar la relación entre los cargadores de clases, hay un beneficio obvio de que las clases de Java tienen una jerarquía de prioridad junto con sus cargadores de clases.

2) Si no se utiliza el modelo de delegación principal y cada cargador de clases lo carga solo, si el usuario escribe una clase java.lang.Object y la coloca en ClassPath del programa, habrá múltiples diferencias en el sistema. clase de objeto, el comportamiento más básico en el sistema de tipos de Java no está garantizado, y la aplicación se convertirá en un desastre.

5. ¿Qué escenarios rompen el modelo de delegación parental?

 Los escenarios más comunes son:

1) Cargador de clases de contexto de subprocesos, típico: JDBC utiliza un cargador de clases de contexto de subprocesos para cargar la clase de implementación del controlador

2) La aplicación multiweb de Tomcat

3) OSGI realiza despliegue modular en caliente

6. ¿Por qué romper el modelo de delegación parental?

La razón es realmente muy simple, es decir, el uso del modelo de delegación de padres no puede satisfacer las necesidades, por lo que solo puede destruirse. Aquí, tome Tomcat, que a menudo se pregunta en las entrevistas, como ejemplo.

Sabemos que el contenedor Tomcat puede implementar varias aplicaciones web al mismo tiempo, y es fácil que varias aplicaciones web dependan del mismo paquete jar pero con diferentes versiones. Por ejemplo, la aplicación 1 y la aplicación 2 dependen de Spring, la aplicación 1 usa la versión 3.2.* y la aplicación 2 usa la versión 4.3.*.

Si sigue el modelo de delegación de padres, ¿qué versión se usa en este momento?

De hecho, qué versión no se puede usar, es propensa a problemas de compatibilidad. Por lo tanto, Tomcat solo puede optar por romper el modelo de delegación parental.

7. ¿Cómo romper el modelo de delegación de padres?

La idea de destruir el modelo de delegación parental es similar, aquí hay un ejemplo de Tomcat, que se pregunta a menudo en las entrevistas.

De hecho, el principio es muy simple. Podemos ver que el modificador de método del código fuente del método de carga de clase anterior (loadClass) está protegido, por lo que solo necesitamos los siguientes pasos para destruir el modelo de delegación principal.

1) Heredar ClassLoader, WebappClassLoader en Tomcat hereda la subclase URLClassLoader de ClassLoader.

2) Vuelva a escribir el método loadClass para implementar su propia lógica. No delegue la carga a la clase principal cada vez. Por ejemplo, puede cargarlo localmente primero, lo que destruirá el modelo de delegación principal.

8. ¿El cargador de clases de Tomcat?

El cargador de clases de Tomcat se muestra a continuación:

1) Bootstrap ClassLoader: puede ver que la extensión ClassLoader no se encuentra en la figura anterior. En Tomcat, la extensión ClassLoader está integrada en Bootstrap ClassLoader.

2) System ClassLoader es Application ClassLoader: el cargador de clases del sistema en Tomcat no carga el contenido de la variable de entorno CLASSPATH, pero crea el cargador de clases del sistema a partir de las siguientes bibliotecas de recursos.

  • $CATALINA_HOME/bin/bootstrap.jar, contiene el método main() utilizado para inicializar el servidor Tomcat y las clases de implementación del cargador de clases de las que depende.
  • $CATALINA_BASE/bin/tomcat-juli.jar o $CATALINA_HOME/bin/tomcat-juli.jar, la clase de implementación de registro.
  • Si tomcat-juli.jar existe en $CATALINA_BASE/bin, utilícelo en lugar del que está en $CATALINA_HOME/bin.
  • $CATALINA_HOME/bin/commons-daemon.jar

3) Common ClassLoader: como puede verse por el nombre, contiene principalmente algunas clases comunes, que son visibles para las clases internas de Tomcat y todas las aplicaciones web.

Las ubicaciones que busca el cargador de clases están definidas por la propiedad common.loader en $CATALINA_BASE/conf/catalina.properties, la configuración predeterminada buscará las siguientes ubicaciones en orden.

  • Clases y recursos sin empaquetar en $CATALINA_BASE/lib
  • Archivos JAR en el directorio $CATALINA_BASE/lib
  • Clases y recursos sin empaquetar en $CATALINA_HOME/lib
  • Archivos JAR en el directorio $CATALINA_HOME/lib

4) WebappX ClassLoader: Tomcat crea un cargador de clases separado para cada aplicación web implementada, lo que garantiza el aislamiento entre diferentes aplicaciones, y las clases y los recursos son invisibles para otras aplicaciones web. La ruta cargada es la siguiente:

  • Todas las clases y recursos sin empaquetar en el directorio /WEB-INF/classes de la aplicación web
  • Clases y recursos en archivos JAR en el directorio /WEB-INF/lib de la aplicación web

9. ¿Cuál es el proceso de carga de clases de Tomcat?

El proceso de carga de clases de Tomcat, es decir, la lógica de WebappClassLoaderBase#loadClass es la siguiente.

1) Primero almacene en caché las entradas de recursos localmente y, si se ha cargado, devuelva los datos en el caché directamente.

2) Verifique si la JVM ha cargado la clase y regrese directamente si es así.

3) Compruebe si la clase que se va a cargar es una clase de Java SE y, de ser así, utilice el cargador de clases de BootStrap para cargar la clase y evitar que la clase de la aplicación web sobrescriba la clase de Java SE.

Por ejemplo, escribió una clase java.lang.String y la colocó en /WEB-INF/classes de la aplicación actual. Si no hay garantía de este paso, entonces usted mismo definirá la clase String utilizada en el proyecto, no rt .jar a continuación, puede dar lugar a muchos peligros ocultos.

4) Para la visualización del delegado del atributo de delegado establecida en verdadero, o algunas clases especiales (javax, algunas clases en el paquete org), use el modo de delegación principal para cargar, y solo algunas partes usan el modelo de delegación principal para cargar.

5) Intente cargar la clase desde el local. Si la carga falla en el paso 5, irá a este paso. Esto rompe el modelo de delegación principal y se carga primero desde el local.

7) Al ir aquí, significa que falló la carga en el paso 6. Si el modo de delegación principal no se usó antes, se delegará al cargador de clases principal para intentar cargar.

8) Ir de esta manera significa que todos los intentos de carga fallan y se lanza ClassNotFoundException.

10. El principio de JDBC utilizando el cargador de clases de contexto de subprocesos

Java define de manera uniforme las clases básicas relacionadas con las funciones de JDBC. En rt.jar, como DriverManager, que carga Bootstrap ClassLoader, las clases de implementación de JDBC están en los paquetes jar de implementación de varios fabricantes. Por ejemplo, MySQL está en mysql En -connector-java, oracle y sqlserver también tendrán sus propios archivos jar de implementación.

En este momento, la clase básica de JDBC necesita llamar al código de la interfaz de proveedor de servicios de JDBC (SPI, Interfaz de proveedor de servicios) implementada por otros fabricantes y desplegada bajo ClassPath de la aplicación. Cuando la clase A llama a la clase B, el cargador de clases de la clase A carga la clase B, y Bootstrap ClassLoader carga las clases básicas de JDBC, pero Bootstrap ClassLoader no sabe y no cargará el código implementado por estos fabricantes.

Por lo tanto, Java proporciona un cargador de clases de contexto de subprocesos, lo que permite establecer y obtener el cargador de clases de contexto del subproceso actual a través de Thread#setContextClassLoader/Thread#getContextClassLoader(). Si no se establece cuando se crea el subproceso, heredará el subproceso principal. Si no se establece en el ámbito global de la aplicación, el cargador de clases es el cargador de clases de la aplicación de forma predeterminada.

En resumen, JDBC puede darse cuenta del comportamiento del cargador de clases principal que "delega" el cargador de subclases para completar la carga de clases a través del cargador de clases de contexto de subprocesos, lo que obviamente no cumple con el modelo de delegación principal, pero este también es el defecto de la delegación principal. modelo mismo causado.

finalmente

Soy Jon Hui, un programador que insiste en compartir productos técnicos originales .

Supongo que te gusta

Origin blog.csdn.net/v123411739/article/details/119700990
Recomendado
Clasificación