JVM 类加载器_4(源码分析)

getSystemClassLoader()方法源代码解析

    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

    从上文可以看出调用了initSystemClassLoader方法。scl是一个ClassLoader对象的引用,下面是安全部分的代码,先跳过。那么我们就来看看initSystemClassLoader()方法做了什么事情。

    private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }

    sclSet是一个布尔类型的对象,表示scl是否被设置值。所以做了一个双重判断,sclSet为false的时候,scl应该是为空的。否则调用sun.misc.Launcher.getLauncher()方法获得Launcher的一个引用。我们待会再看这个方法。假设方法返回了一个Launcher对象,将Launcher中的ClassLoader成员变量赋值给了scl。并且在权限安全的情况下,new了一个SystemClassLoaderAction,传入了scl。最后把sclSet赋值为true。这个方法中有两个方法需要讲解,一个是sun.misc.Launcher.getLauncher()。另外一个是new SystemClassLoaderAction(scl)

    我们先来看看sun.misc.Launcher.getLauncher()

    ……
    private static Launcher launcher = new Launcher();
    ……
    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

    只写了一部分,大概的流程是:先获取了扩展类加载器的对象(是Launcher的静态内部类,通过加载ExtDirs文件夹下面的jar包,存为文件并进行解析),然后将获得的扩展类加载器的对象传给AppClassLoader,生成一个AppClassLoader的对象,并将其赋值给Launcher类的成员变量:loader。且将线程的上下文类加载器设置为该AppClassLoader的引用。下面是安全的代码,先跳过。

    new SystemClassLoaderAction(scl))做了啥呢?

class SystemClassLoaderAction
    implements PrivilegedExceptionAction<ClassLoader> {
    private ClassLoader parent;

    SystemClassLoaderAction(ClassLoader parent) {
        this.parent = parent;
    }

    public ClassLoader run() throws Exception {
        String cls = System.getProperty("java.system.class.loader");
        if (cls == null) {
            return parent;
        }

        Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
        ClassLoader sys = (ClassLoader) ctor.newInstance(
            new Object[] { parent });
        Thread.currentThread().setContextClassLoader(sys);
        return sys;
    }
}

    这是一个实现了PrivilegedExceptionAction的类,所以会自动调用run方法。run方法里就是看java.system.class.loader是否给赋值,(这是我们之前自定义系统类加载器的参数还记得吗?),如果没有赋值直接返还系统类加载器(传进来的scl),否则根据带ClassLoader参数的构造方法new一个新的对象,并且将这个新new出来的对象作为当前线程的上线文类加载器。将scl作为新的系统类加载器的父加载器

    最后返回的scl即经过这些步骤后的scl

当前类加载器 Current ClassLoader

    每个类都会尝试用它自己的加载器(即加载自身的加载器)来尝试加载这个类依赖的其他类.

    例如ClassX引用了ClassY那么加载X的类加载器就会去加载Y(前提是Y没有被加载)

    线程上下文类加载器是从JDK1.2开始引入的。类Thread中getContextClassLoader()与setContextClassLoader(ClassLoader cl)分别用来设置和获取上下文类加载器。

    如果没有通过setContextClassLoader(ClassLoader cl)进行设值的话,线程将继承其父线程的类加载器

    Java运行时的初始线程的上下文加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源。

    SPI(Service Provider Interface)

    线程上下文类加载器的重要性:

  • 父ClassLoader可以使用当前线程的Thread.currentThread().getContextClassLoader()所指定的classloader加载的类。这就改变类父ClassLoader或是其他没有父子关系的ClassLoader加载类的情况,即改变了双亲委托模型。

  • 在双亲委托模型下,类加载器是由下至上的,但是对于SPI来说,有些接口是由Java核心库提供的,而Java核心库是由启动类加载器加载的,而这些接口的实现却来自于不同的jar包(厂商),Java的启动类加载器是不会加载其他来源的jar包的,这样传统的双亲委托机制就不满足需求。而通过当前线程设制相应的上下文加载器,就可以由设置的上下文类加载器来实现对接口实现类的加载

上下文类加载器SeviceLoader源码分析

public class MyTest26 {
    public static void main(String[] args) {
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> it = loader.iterator();
        while (it.hasNext()) {
            Driver driver = it.next();
            System.out.println("class: " + driver.getClass() + ", classLoader: " + driver.getClass().getClassLoader());
        }

        System.out.println("线程上下文类加载器:" + Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader的类加载器:" + ServiceLoader.class.getClassLoader());
    }
}

结果:

class: class com.mysql.jdbc.Driver, classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
class: class com.mysql.fabric.jdbc.FabricMySQLDriver, classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
线程上下文类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器:null

    我们来看一下ServiceLoader的Document(太长了。。下次复习自己再翻译一遍。。不想写出来献丑了)

    针对于ServiceLoader.load(Driver.class);源代码中的一些重要部分做一个讲解:

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

    将当前线程的类加载器赋值给了重载的load方法。

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

    生成了一个ServiceLoader的对象。

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    将当前线程的上下文加载器赋给了loader,如果是空的则给系统类加载器。将已经加载过了的服务提供者(providers)进行清空。new一个延迟加载的对象,将服务接口和加载器都传入进去。
    至此,load方法执行结束了,一些初始化过程已经完成了。

    接下来我们来分析下面的代码:
JVM 类加载器_4(源码分析)_第1张图片
    下面是iterator的代码:

    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

    返回的是new的一个新的Interator。生成providers的一个迭代器。providers是服务提供者的map。重写了hasNext()方法和next()、remove()方法.
    接下来我们一个个来分析:

  • JVM 类加载器_4(源码分析)_第2张图片
    进入到延迟加载到hasNext()方法
    JVM 类加载器_4(源码分析)_第3张图片
    JVM 类加载器_4(源码分析)_第4张图片
    JVM 类加载器_4(源码分析)_第5张图片
    nextname一开始是为空的,通过PREFIX和从服务接口获取的二进制名字获得fullname(也就是从META-INF/services/java.sql.Driver)文件。通过传入的类加载器,loader.getResources(fullName)获得这个文件的Enumeration(资源),如果hasMoreElements(有更多的资源),通过URL打开流,加载文件中的内容,每一行开始读,并且放入names的ArrayList当中,放回这个ArrayList的迭代器,将这个迭代器赋值给pending.然后将下一个元素赋值给nextName并且返回true
  • JVM 类加载器_4(源码分析)_第6张图片
    next方法也会调用ServiceLoader的next方法,也就是刚刚返回的Iterator中的next方法:
    JVM 类加载器_4(源码分析)_第7张图片
    我们来看看延迟加载中的next方法做了什么:
    JVM 类加载器_4(源码分析)_第8张图片
    同样的,看下nextService方法:
        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

    hasNextService()刚刚分析过,看是否有下一个元素,如果没有直接抛出异常了,cn为nextName。即在hasNextService方法最后将name赋值为pending.next()的一个元素。也就是META-INF/services/java.sql.Driver文件中的一个二进制名字。然后调用Class.forName方法,就之前ServiceLoader.load()方法赋值的loader来加载这个二进制类名,并且初始化它。将初始化完成的对象放入providers中(key为二进制名字,value为实例号的对象,并将这个对象返回)。
    至此,我们拿到了Driver服务的一个服务提供者对象。并且打印出来

ServiceLoader作业

下面两行代码发生了什么事情?

public class MyTest27 {
    public static void main(String[] args) throws Exception{
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc://mysql://localhost", "username", "password");
    }
}

    一开始看的太浅了。就只看了forName方法和getConnection方法。这个作用的其一重要点在于:Class.forName会导致被加载的二进制名字的类初始化。且,调用某个类的静态方法,也会导致直接定义这个静态方法的类的初始化。
    来分析源码吧,分两个语句就用两个标题

Class.forName(“com.mysql.jdbc.Driver”);

JVM 类加载器_4(源码分析)_第9张图片

    Reflection.getCallerClass();返回调用这个方法所在的类的对象,在这里也就是MyTest27的Class对象。用MyTest27的类加载器加载给定的二进制名字,并且初始化这个二进制名字所代表的类。
    那么我们就需要看Driver类的初始化会导致什么样的代码执行
JVM 类加载器_4(源码分析)_第10张图片
    会执行这部分静态代码块。由于对DriverManager类中的静态方法的主动调用,会导致DriverManager类的初始化,会先初始化DriverManager类,在调用registerDriver方法。我们先看看DriverManager类初始化完成了什么样的工作:

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
        private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

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

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

    分析一下loadInitialDrivers方法。一开始的jdbc.drivers为空(可以手动自己运行一下看看),第二个全线中的RUN方法就是我们上一节讲解的源代码。将META-INF/services/java.sql.Driver文件下的所有二进制名字加载且初始化放入providers容器中。然后就返回了,因为drivers为空。
    再返还到registerDriver方法,将自身注册进registeredDrivers去(我感觉这边addIfAbsent方法会将driver下的所有实现了的方法进行一个递归添加,所以Driver服务下有几个服务提供者就会加多少次):
JVM 类加载器_4(源码分析)_第11张图片

DriverManager.getConnection源代码解析

    直接分析重要的代码
JVM 类加载器_4(源码分析)_第12张图片

JVM 类加载器_4(源码分析)_第13张图片
JVM 类加载器_4(源码分析)_第14张图片
JVM 类加载器_4(源码分析)_第15张图片

接下来在进行连接等操作。

你可能感兴趣的:(JVM)