我在java-nio中偷窥了类加载器

一点一滴,水滴石穿

最近重温nio与socket,把源码看了一遍,做了一些笔记。
由于前段时间和同事讨论了一下类加载器,这一次看了socket相关的源码,看到了SPI,联想了一下,类加载器真是无处不在,原来是这么玩的。

起源 Selector.open

通过java的nio,做了基于socket的文件传输,想着netty底层实现要温故而知新才行。

Selector selector = Selector.open();

从open方法进去,可以看到provider

provider的作用

/**
     * Opens a selector.
     *
     * <p> The new selector is created by invoking the {@link
     * java.nio.channels.spi.SelectorProvider#openSelector openSelector} method
     * of the system-wide default {@link
     * java.nio.channels.spi.SelectorProvider} object.  </p>
     *
     * @return  A new selector
     *
     * @throws  IOException
     *          If an I/O error occurs
     */
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

provider的作用主要是:打开DatagramChannel、Pip、Selector、channels、ServerSocketChannel、SocketChannel。对应的是:java.nio.channels.DatagramChanneljava.nio.channels.Pipjava.nio.channels.Selectorjava.nio.channels.ServerSocketChanneljava.nio.channels.SocketChannel

 /**
     * Returns the system-wide default selector provider for this invocation of
     * the Java virtual machine.
     *
<此处省略了一些>
     * <p> Subsequent invocations of this method return the provider that was
     * returned by the first invocation.  </p>
     *
     * @return  The system-wide default selector provider
     */
    public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

看到这里就比较有意思了。可以看到,在构造provider的时候,采用了三种方式,

  1. 从property中加载(loadProviderFromProperty)
  2. 如果没有system.setProperty的方式配置Provider,那么就采用jar能够加载的类路径方式(例如ext、application)加载provider(loadProviderAsService)
  3. 都没有的话,才采用默认的方式,sun.nio.ch.DefaultSelectorProvider.create()。

SelectorProvider中的类加载

private static boolean loadProviderAsService() {

        ServiceLoader<SelectorProvider> sl =
            ServiceLoader.load(SelectorProvider.class,
                               ClassLoader.getSystemClassLoader());
        Iterator<SelectorProvider> i = sl.iterator();
        for (;;) {
            try {
                if (!i.hasNext())
                    return false;
                provider = i.next();
                return true;
            } catch (ServiceConfigurationError sce) {
                if (sce.getCause() instanceof SecurityException) {
                    // Ignore the security exception, try the next provider
                    continue;
                }
                throw sce;
            }
        }
    }

这里我们重点看看类加载相关的,也就是loadProviderAsService,里边使用了一个ServiceLoader的类加载器来加载具体的SelectorProvider实现类,我们从包名 java.nio.channels.spi.SelectorProvider 和所在的rt.jar,可以了解到这个方法属于java的基础类,在1.4版本的时候就定义好了。

SystemClassLoader加载的是那些jar?

接着,可以先看看 ClassLoader.getSystemClassLoader() , 这个方法是获取系统的类加载器。
这里简单看看,首先,我们可以了解一下熟悉的类加载器的加载顺序:boot、ext、app、自定义。

但是这里SystemClassLoader是属于那一层的加载呢?
在getSystemClassLoader进去可以看到有个 initSystemClassLoader

@CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }
    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();
                    }
                }
             // <此处省略一些代码>
            }
            sclSet = true;
        }
    }

sun.misc.Launcher.getLauncher() , 到底用的是哪一个的类加载器呢?我们可以从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);
       
// <此处省略一些代码>
        }

    }

最后的loader 落在了AppClassLoader上,也就是说,只要在app以上的类加载路径(根据boot、ext、app的顺序),也只有ext与app这两个路径(一个是jre/ext/*路径,一个是java.class.path)只要被加载到,就会被这个 loadProviderAsService 调用。

service的类加载器

接着我们继续看回 ServiceLoader

 /**
     * Creates a new service loader for the given service type and class
     * loader.
     *
     * @param  <S> the class of the service type
     *
     * @param  service
     *         The interface or abstract class representing the service
     *
     * @param  loader
     *         The class loader to be used to load provider-configuration files
     *         and provider classes, or <tt>null</tt> if the system class
     *         loader (or, failing that, the bootstrap class loader) is to be
     *         used
     *
     * @return A new service loader
     */
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

SelectorProvider使用的是SystemClassLoader的load重载方法,但是这里我也注意到了load另外一个方法

/**
     * Creates a new service loader for the given service type, using the
     * current thread's {@linkplain java.lang.Thread#getContextClassLoader
     * context class loader}.
     *
     * <p> An invocation of this convenience method of the form
     *
     * <blockquote><pre>
     * ServiceLoader.load(<i>service</i>)</pre></blockquote>
     *
     * is equivalent to
     *
     * <blockquote><pre>
     * ServiceLoader.load(<i>service</i>,
     *                    Thread.currentThread().getContextClassLoader())</pre></blockquote>
     *
     * @param  <S> the class of the service type
     *
     * @param  service
     *         The interface or abstract class representing the service
     *
     * @return A new service loader
     */
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

这里可以看到 Thread.currentThread().getContextClassLoader(), 这种方式有什么用呢?

SPI调用

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

load被很多方法调用到,这里我们就选用比较经常会用到的DriverManager来看看。DriverManager来自rt.jar,是核心的基础类,那么如果是按照双亲委托的方式,这个Driver的实现类,如果不是存放在rt.jar能够读到的系统路径上(ext、app)是加载不到的。

Driver被很多本地的jdbc驱动jar所实现
但是一般来说,jdbc的驱动包是放在项目下的,所以这里需要打破双亲委托。load只有一个参数的重载方法中, Thread.currentThread().getContextClassLoader(),就是用来打破双亲委托机制的。
它获取的是当前线程的上下文类加载器,只要获得上下文类加载器,那么就可以无视双亲委托机制啦

下面分享一下深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)中关于打破双亲委托机制。
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章