¿Estás seguro de que realmente entiendes la "delegación parental"? !

¿Por qué no conoces el camino para convertirte en un dios de los ingenieros de Java en GitHub 19k Star?

Recientemente, durante el proceso de entrevista, me gusta hacer algunas preguntas sobre el nombramiento de mis padres, porque descubrí que esta pregunta realmente puede ayudarme a comprender a un candidato en todos los aspectos.

Recuerdo que durante una entrevista hace unos días, hablé con un candidato sobre el mecanismo de carga de clases de la JVM, habló sobre la delegación de los padres y me dijo con confianza su comprensión de la delegación de los padres.

Debido a que es raro encontrar un candidato que sepa mucho sobre conocimientos, iniciamos una confrontación de "ronda 300" Después de hacer estas preguntas, ha pasado alrededor de media hora.

Al final, la persona de seguimiento me dijo: "¡¡ ¡Nunca esperé que yo, un gerente técnico que había trabajado durante 7 años, fuera asignado a abuso por mis padres !!! "

Revisemos las preguntas que le hice y veamos cuántas respuestas puedes responder:

1. ¿Qué es la delegación de los padres? 2. ¿Por qué los padres necesitan delegar y cuáles son los problemas con no delegar? 3. ¿Se hereda la relación entre "cargador principal" y "cargador secundario"? 4. ¿Cómo funciona la delegación de los padres? 5. ¿Puedo destruir voluntariamente este mecanismo de delegación parental? ¿Cómo destruirlo? 6. ¿Por qué anular el método loadClass puede romper la delegación principal? ¿Cuál es la diferencia entre este método y findClass () y defineClass ()? 7. Cuénteme acerca de los ejemplos de delegación de los padres que sabe acerca de la ruptura 8. ¿Por qué JNDI, JDBC, etc. necesitan romper la delegación de los padres? 9. ¿Por qué TOMCAT destruye la delegación de los padres? 10. ¡Hable sobre su comprensión de la tecnología modular!

Las 10 preguntas anteriores, comienzan desde el principio, ¿a cuántas preguntas probablemente puede atenerse?

¿Qué es el mecanismo de delegación principal?

En primer lugar, sabemos que la máquina virtual necesita usar un cargador de clases para cargar la clase. En Java, hay muchos cargadores de clases. Entonces, cuando la JVM quiere cargar un archivo .class, ¿qué clase debe usarse? ¿Qué pasa con la carga del cargador?

Esto tiene que mencionar el "mecanismo de delegación de padres".

En primer lugar, debemos saber que los siguientes 4 tipos de cargadores son compatibles con el sistema de lenguaje Java:

  • Bootstrap ClassLoader Bootstrap ClassLoader
  • Cargador de clases de extensión Cargador de clases extendido estándar
  • Aplicación ClassLoader Aplicación ClassLoader
  • Cargador de clases de usuario Cargador de clases definido por el usuario

Existe una relación jerárquica entre estos cuatro tipos de cargadores, como se muestra a continuación.

-w704

En general, se considera que el cargador de la capa superior es el cargador principal del cargador de la siguiente capa, por lo que, a excepción de BootstrapClassLoader, todos los cargadores tienen un cargador principal.

Entonces, el llamado mecanismo de delegación principal se refiere a: cuando un cargador de clases recibe una solicitud de carga de clases, no cargará directamente la clase especificada, sino que delegará la solicitud a su cargador principal para cargar . Solo cuando el cargador principal no pueda cargar la clase, el cargador actual será responsable de la carga de la clase.

Entonces, ¿bajo qué circunstancias el cargador principal no podrá cargar una determinada clase?

De hecho, los cuatro tipos de cargadores proporcionados en Java tienen sus propias responsabilidades:

  • Bootstrap ClassLoader es principalmente responsable de cargar las bibliotecas de clases principales de Java, rt.jar, resources.jar, charsets.jar y class en% JRE_HOME% \ lib.
  • Extention ClassLoader es principalmente responsable de cargar paquetes jar y archivos de clase en el directorio% JRE_HOME% \ lib \ ext.
  • Application ClassLoader, principalmente responsable de cargar todas las clases bajo el classpath de la aplicación actual
  • User ClassLoader, cargador de clases definido por el usuario, puede cargar el archivo de clase de la ruta especificada

En otras palabras, los cargadores Bootstrap y Extension no cargarán una clase definida por el usuario, como com.hollis.ClassHollis.

¿Por qué los padres necesitan delegar?

Como mencionamos anteriormente, debido a la estricta relación jerárquica entre los cargadores de clases, la clase Java también tiene una relación jerárquica.

En otras palabras, esta relación jerárquica es prioritaria.

Por ejemplo, una clase definida en el paquete java.lang, debido a que está almacenada en rt.jar, será confiada a Bootstrap ClassLoader y finalmente cargada por Bootstrap ClassLoader.

Y una clase com.hollis.ClassHollis definida por el usuario, siempre se confiará a Bootstrap ClassLoader, pero como Bootstrap ClassLoader no es responsable de cargar la clase, será cargada por Extension ClassLoader, y Extension ClassLoader no es responsable de esta clase La carga finalmente será cargada por Application ClassLoader.

Este mecanismo tiene varias ventajas.

Primero, mediante la delegación, se puede evitar la carga repetida de clases.Cuando el cargador padre ya ha cargado una determinada clase, el cargador hijo no recargará esta clase.

Además, la seguridad también está garantizada a través de la forma de delegación de los padres . Porque cuando se carga Bootstrap ClassLoader, solo cargará las clases en el paquete jar en JAVA_HOME, como java.lang.Integer, entonces esta clase no será reemplazada a voluntad, a menos que alguien corra a su máquina y destruya su JDK.

Entonces, puede evitar que alguien cargue un java.lang.Integer personalizado con una función destructiva. Esto puede evitar de forma eficaz que se manipule la API principal de Java.

¿La relación entre el "cargador principal y el secundario" es herencia?

Mucha gente ve nombres como cargador principal y cargador secundario, y piensan que existe una relación de herencia entre los cargadores de clases en Java.

Incluso muchos artículos en Internet tienen opiniones erróneas similares.

Es necesario aclarar aquí que en el modelo de delegación padre, la relación padre-hijo entre cargadores de clases generalmente no se implementa en una relación de herencia, sino que usa una relación de composición para reutilizar el código del cargador padre.

La siguiente es la definición del cargador padre en ClassLoader:

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;
}

¿Cómo funciona la delegación de los padres?

El modelo de delegación padre es importante para garantizar el funcionamiento estable de los programas Java, pero su implementación no es complicada.

El código para implementar la delegación principal se concentra en el método loadClass () de java.lang.ClassLoader :

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    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;
        }
    }

El código no es difícil de entender, principalmente los siguientes pasos:

1. Primero verifique si la clase ha sido cargada. 2. Si no está cargada, llame al método loadClass () del cargador principal para cargarla. 3. Si el cargador principal está vacío, el cargador de clases de inicio se utiliza como cargador principal por defecto. 4. Si la clase principal no se carga, después de lanzar ClassNotFoundException, llame a su propio método findClass () para cargar.

¿Cómo destruir activamente el mecanismo de delegación principal?

Conociendo la realización del modelo de delegación parental, es muy sencillo destruir el mecanismo de delegación parental.

Debido a que el proceso de delegación de sus padres se implementa en el método loadClass, si desea destruir este mecanismo, puede personalizar un cargador de clases y anular el método loadClass para que no realice la delegación principal.

loadClass () 、 findClass () 、 defineClass () 区别

Hay muchos métodos relacionados con la carga de clases en ClassLoader. LoadClass se mencionó anteriormente. Además, también existen findClass y defineClass. Entonces, ¿cuál es la diferencia entre estos métodos?

  • loadClass ()
    • Es el método principal para la carga de clases y el mecanismo de delegación principal predeterminado se implementa en este método.
  • findClass ()
    • Cargar .class bytecode según el nombre o la ubicación
  • definclass ()
    • Convertir código de bytes en clase

Necesitamos expandir loadClass y findClass.Como dijimos antes, cuando queremos personalizar un cargador de clases y nos gusta romper el principio de delegación principal, reemplazaremos el método loadClass.

Entonces, ¿qué pasa si queremos definir un cargador de clases, pero no queremos romper el modelo de delegación padre?

En este momento, puede heredar ClassLoader y anular el método findClass. El método findClass () es un nuevo método agregado por ClassLoader después de JDK1.2.

 /**
 * @since  1.2
 */
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

Este método solo genera una excepción, no hay una implementación predeterminada.

Después de JDK1.2, ya no se recomienda que los usuarios anulen directamente el método loadClass (), pero se recomienda implementar su propia lógica de carga de clases en el método findClass ().

Porque en la lógica del método loadClass (), si el cargador de clases padre no se carga, llamará a su propio método findClass () para completar la carga.

Por lo tanto, si desea definir su propio cargador de clases y seguir el modelo de delegación parental, puede heredar ClassLoader e implementar su propia lógica de carga en findClass.

Ejemplos de delegación parental rota

La destrucción del mecanismo de delegación padre no es inusual, muchos marcos y contenedores destruirán este mecanismo para lograr ciertas funciones.

La primera situación en ser destruida es ante la delegación de los padres.

Dado que el modelo de delegación padre se introdujo después de JDK 1.2, los cargadores de clases definidos por el usuario ya estaban en uso antes de esto. Por tanto, estos no siguen el principio de delegación de los padres.

El segundo es el caso en el que JNDI, JDBC, etc.necesitan cargar la clase de implementación de la interfaz SPI.

El tercero es implementar herramientas de implementación en caliente intercambiables en caliente. Para que el código entre en vigor dinámicamente sin reiniciar, el módulo y el cargador de clases se reemplazan juntos para realizar el reemplazo en caliente del código.

El cuarto es la aparición de contenedores web como tomcat.

El quinto es la aplicación de tecnologías modulares como OSGI y Jigsaw.

¿Por qué JNDI, JDBC, etc. necesitan romper la delegación de los padres?

En nuestro desarrollo diario, la mayoría de las veces llamamos a esas clases básicas proporcionadas por Java a través de API, que son cargadas por Bootstrap.

Sin embargo, además de la API, también existe un método SPI.

Como servicio típico de JDBC, normalmente creamos una conexión de base de datos de las siguientes formas:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");

Antes de que se ejecute el código anterior, DriverManager será cargado por el cargador de clases primero, porque la clase java.sql.DriverManager se encuentra en rt.jar, por lo que será cargada por el cargador raíz.

Cuando se carga la clase, se ejecutan los métodos estáticos de la clase. Una de las piezas clave del código es:

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

Este código intentará cargar todas las clases de implementación bajo la ruta de clases que implementan la interfaz del controlador.

Entonces, aquí viene el problema.

DriverManager lo carga el cargador raíz, por lo que cuando encuentre el código anterior al cargar, intentará cargar todas las clases de implementación del controlador, pero estas clases de implementación son básicamente proporcionadas por terceros. De acuerdo con el principio de delegación principal, las clases de terceros no pueden ser Se carga el cargador raíz.

Entonces, ¿cómo solucionar este problema?

Por lo tanto, el principio de delegación principal se destruyó al introducir ThreadContextClassLoader (cargador de contexto de subprocesos, AppClassLoader por defecto) en JDBC.

Podemos ver cuando nos sumergimos en el método ServiceLoader.load:

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

En la primera línea, obtenga el cargador de clases de contexto de subprocesos actual AppClassLoader, que se utiliza para cargar clases de implementación específicas en la ruta de clases.

¿Por qué Tomcat destruye la delegación parental?

Sabemos que Tomcat es un contenedor web, por lo que un contenedor web puede necesitar implementar múltiples aplicaciones.

Diferentes aplicaciones pueden depender de diferentes versiones de la misma biblioteca de clases de terceros, pero el nombre de ruta completo de una determinada clase en diferentes versiones de la biblioteca de clases puede ser el mismo.

Por ejemplo, varias aplicaciones deben depender de hollis.jar, pero la aplicación A debe depender de la versión 1.0.0, pero la aplicación B debe depender de la versión 1.0.1. Una clase en ambas versiones es com.hollis.Test.class.

Si se adopta el mecanismo predeterminado de carga de clases delegadas por los padres, es imposible cargar varias clases idénticas.

Por lo tanto, Tomcat rompe el principio de delegación parental, proporciona un mecanismo de aislamiento y proporciona un cargador WebAppClassLoader independiente para cada contenedor web.

Mecanismo de carga de clases de Tomcat: para lograr el aislamiento, se da prioridad a las clases de carga definidas por la aplicación web en sí, por lo que no sigue la convención de delegación parental. El cargador de clases propio de cada aplicación: WebAppClassLoader es responsable de cargar los archivos de clase en su propio directorio. CommonClassLoader lo cargará más tarde, que es lo opuesto a la delegación parental.

Tecnología de modularización y mecanismo de carga de clases.

En los últimos años, la tecnología de modularización ha sido muy madura y la tecnología de modularización se ha aplicado en JDK 9.

De hecho, ya antes de JDK 9, el marco de OSGI era modular, y la razón por la que OSGI pudo lograr el intercambio en caliente de módulos y el control preciso de la visibilidad interna del módulo se atribuyó a su mecanismo de carga de clase especial, entre cargadores. La relación ya no es la estructura de árbol del modelo de delegación principal, sino que se convierte en una estructura de red compleja.

-w942

En JDK, la delegación de los padres no es absoluta.

Antes de JDK9, las clases básicas de JVM solían estar en el paquete rt.jar, que también es la piedra angular de la operación de JRE.

Esto no solo es una violación del principio de responsabilidad única, sino que también se empaquetan muchas clases inútiles cuando se compila el programa, lo que provoca hinchazón.

En JDK9, todo el JDK se construye en base a la modularidad. El rt.jar y tool.jar anteriores se dividen en docenas de módulos. Al compilar, solo se compilan los módulos realmente utilizados y cada cargador de clases realiza sus propias tareas. Responsable, solo carga los módulos de los que eres responsable.

Class<?> c = findLoadedClass(cn);
if (c == null) {
    // 找到当前类属于哪个模块
    LoadedModule loadedModule = findLoadedModule(cn);
    if (loadedModule != null) {
        //获取当前模块的类加载器
        BuiltinClassLoader loader = loadedModule.loader();
        //进行类加载
        c = findClassInModuleOrNull(loadedModule, cn);
     } else {
          // 找不到模块信息才会进行双亲委派
            if (parent != null) {
              c = parent.loadClassOrNull(cn);
            }
      }
}

para resumir

Arriba, de lo que es la delegación de los padres, de cómo realizar y destruir la delegación de los padres, y de los ejemplos de destrucción de la delegación de los padres, hemos introducido el conocimiento de la delegación de los padres de manera integral.

Creo que al estudiar este artículo, debe tener una comprensión más profunda del mecanismo de delegación de los padres.

Después de leer este artículo, escribí al revés en mi currículum: Familiarizado con el mecanismo de carga de clases de Java y pregunte si no está satisfecho

Sobre el autor: serie de artículos de Hollis , el autor tiene una búsqueda única de codificación de personas, expertos técnicos de Alibaba, "tres cursos del programador", coautor, "ingenieros de Java a la manera de Dios".

Si tiene algún comentario, sugerencia o desea comunicarse con el autor, puede seguir la cuenta oficial [ Hollis ] y dejarme un mensaje directamente en segundo plano.

Supongo que te gusta

Origin blog.csdn.net/hollis_chuang/article/details/112462198
Recomendado
Clasificación