JVM 类加载器

xiaoxiao2021-09-13  44

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方法执行结束了,一些初始化过程已经完成了。

    接下来我们来分析下面的代码:     下面是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()方法.     接下来我们一个个来分析:

进入到延迟加载到hasNext()方法 nextname一开始是为空的,通过PREFIX和从服务接口获取的二进制名字获得fullname(也就是从META-INF/services/java.sql.Driver)文件。通过传入的类加载器,loader.getResources(fullName)获得这个文件的Enumeration(资源),如果hasMoreElements(有更多的资源),通过URL打开流,加载文件中的内容,每一行开始读,并且放入names的ArrayList当中,放回这个ArrayList的迭代器,将这个迭代器赋值给pending.然后将下一个元素赋值给nextName并且返回true next方法也会调用ServiceLoader的next方法,也就是刚刚返回的Iterator中的next方法: 我们来看看延迟加载中的next方法做了什么: 同样的,看下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”);

    Reflection.getCallerClass();返回调用这个方法所在的类的对象,在这里也就是MyTest27的Class对象。用MyTest27的类加载器加载给定的二进制名字,并且初始化这个二进制名字所代表的类。     那么我们就需要看Driver类的初始化会导致什么样的代码执行     会执行这部分静态代码块。由于对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服务下有几个服务提供者就会加多少次):

DriverManager.getConnection源代码解析

    直接分析重要的代码

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