我在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版)

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