Java类加载机制之加载过程

类加载的过程

在这里插入图片描述
加载–>链接–>初始化 其中链接过程分为验证、准备、解析三个步骤。

加载过程概述

1.通过全限定名获取定义此类的二进制字节流
2.将静态字节码文件加载到内存中(方法区)成为运行时数据结构
3.其中加载过程由类加载器执行,字节码文件加载到内存的同时也会创建一个类对象,作为访问类数据信息的接口

类加载器

在这里插入图片描述
系统自带的三种类加载器:
1.启动类加载器(Bootstrap ClassLoader) 负责加载<JAVA_HOME>\lib下的class文件 C++实现
2.扩展类加载器(Extension ClassLoader) 负责加载<JAVA_HOME>\lib\ext下的class文件
3.应用程序类加载器(Applaction ClassLoader)负责加载用户自定义的Java字节码文件(ClassPath下的类库)
--------------------------------------------------------------------------------------------------------
4.用户自定义的类加载器(User ClassLoader) 负责执行用户自定义的加载规则。

注:
1.上述加载器存在父子类关系,BootStrap ClassLoader的父类为null。
2.判断两个类是否相等的前提是加载该类的类加载器相同

双亲委派机制

什么是双亲委派模型:
指类加载器接收到加载请求不会立即加载,会将加载请求向父类加载器发送,
一直到父类加载器为空(启动类加载器),开始加载,当父类加载器无法加载时,子类加载器开始尝试加载。
为什么用双亲委派模型:
双亲委派模型可是使Java类具有优先级,系统底层的类优先被加载,
可以防止用户自定义和jdk源码包中同名类从而破坏Java基本执行。

双亲委派模型的实现(jdk 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;
        }
    }

如何破坏双亲委派

1.可以通过自定义类加载器(直接重写loadClass方法,自定义加载规则),使用自定义的类加载器进行加载
2.所有涉及SPI(Servoice Provider Interface)的加载动作,如JDBC服务破坏了双亲委派
为什么说JDBC破坏了双亲委派?
Java的rt.jar中有关于JDBC驱动Driver的接口,不同数据库的厂家针对该接口进行了驱动的实现,
所以在JDBC进行驱动加载时,理应加载已经实现的驱动包(如mysql-connector-java,导入项目后位于ClassPath),
但是由于rt.jar中已经存在Driver的接口,所以按照双亲委派,则加载应该是位于rt.jar的接口文件。
此时就需要破坏双亲委派。。
JDBC服务如何破坏?
使用线程上下文类加载器,线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。至于线程上下文加载器如何破坏可以参见JDBC中mysql驱动包的源码DriverManager(Driver类只实现注册功能,将MySQL Driver实例注册给DriverManager)。

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

用户自定义类加载器:

1.继承ClassLoader类,重写findClass()方法,此时所写的类加载器仍然满足双亲委派。
2.继承ClassLoader类,直接重写loadClass()方法,破坏双亲委派规则(坑很多)。
3.使用Class.forName()可以指定类加载器进行加载。

loadClass()方法说明:定义了双亲委派的加载规则,最后所有父类都无法加载时采用findClass()方法进行加载。 ClassLoader中的findClass方法没有定义任何加载过程。
findClass()方法说明:自定义的加载规则写在该方法内,由loadClass()进行调用。
defClass()方法说明:进行自定义加载时,可使用该方法将加载的普通字节流文件转化java.lang.Class文件。

什么情况下需要自定义类加载器

1.系统加载器无法加载,系统自带的类加载器只能加载jdk源码字节码文件以及位于ClassPath下的类库。当所要加载的字节码文件不在这些路径下时,需要自定义。
2.有时我们不一定是从类文件中读取类,可能是从网络的输入流中读取类,这就需要做一些加密和解密操作,这就需要自己实现加载类的逻辑,当然其他的特殊处理也同样适用。
3.可以定义类的实现机制,实现类的热部署,如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的。

猜你喜欢

转载自blog.csdn.net/weixin_43695091/article/details/89424324