JDK源码架构解读

JavaClassLoader源码分析(下)

2019-12-27  本文已影响0人  spring_coderman
URLClassLoader.png 构造方法.png 构造方法解读.jpg URL获取和设置.jpg

下面我把成员变量和静态代码块放在一块了

    //URLClassLoader通过URLClassPath构建要扫描的类路径信息
    //一个ucp包含一个URL对象的数组
   /* The search path for classes and resources */
    private final URLClassPath ucp;

    /* The context to be used when loading classes and resources */
    private final AccessControlContext acc;

  /* A map (used as a set) to keep track of closeable local resources
     * (either JarFiles or FileInputStreams). We don't care about
     * Http resources since they don't need to be closed.
     *
     * If the resource is coming from a jar file
     * we keep a (weak) reference to the JarFile object which can
     * be closed if URLClassLoader.close() called. Due to jar file
     * caching there will typically be only one JarFile object
     * per underlying jar file.
     *
     * For file resources, which is probably a less common situation
     * we have to keep a weak reference to each stream.
     */

    private WeakHashMap<Closeable,Void>
        closeables = new WeakHashMap<>();


    static {
        sun.misc.SharedSecrets.setJavaNetAccess (
            new sun.misc.JavaNetAccess() {
                public URLClassPath getURLClassPath (URLClassLoader u) {
                    return u.ucp;
                }

                public String getOriginalHostName(InetAddress ia) {
                    return ia.holder.getOriginalHostName();
                }
            }
        );
       //注册并行加载
        ClassLoader.registerAsParallelCapable();
    }

获取资源方法

 /**
     * Returns an input stream for reading the specified resource.
     * If this loader is closed, then any resources opened by this method
     * will be closed.
     *
     * <p> The search order is described in the documentation for {@link
     * #getResource(String)}.  </p>
     *
     * @param  name
     *         The resource name
     *
     * @return  An input stream for reading the resource, or {@code null}
     *          if the resource could not be found
     *
     * @since  1.7
     */
    public InputStream getResourceAsStream(String name) {
        //这里注意,这里是通过父类(ClassLoader)的方法加载URL资源的
        URL url = getResource(name);
        try {
            if (url == null) {
                return null;
            }
            URLConnection urlc = url.openConnection();
            InputStream is = urlc.getInputStream();
            if (urlc instanceof JarURLConnection) {
                JarURLConnection juc = (JarURLConnection)urlc;
                JarFile jar = juc.getJarFile();
                //这个容器不是并发安全的,因此需要对其进行同步
                synchronized (closeables) {
                    if (!closeables.containsKey(jar)) {
                        closeables.put(jar, null);
                    }
                }
            } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
                //这个容器不是并发安全的,因此需要对其进行同步
                synchronized (closeables) {
                    closeables.put(is, null);
                }
            }
            return is;
        } catch (IOException e) {
            return null;
        }
    }

想加载类,必须先找到类,以及所在的包/Jar文件地址,并解析

/**
     * Finds and loads the class with the specified name from the URL search
     * path. Any URLs referring to JAR files are loaded and opened as needed
     * until the class is found.
     *
     * @param name the name of the class
     * @return the resulting class
     * @exception ClassNotFoundException if the class could not be found,
     *            or if the loader is closed.
     * @exception NullPointerException if {@code name} is {@code null}.
     */
     //上面的注释说明JAR文件也是一种URL资源,当需要加载类的时候就需要先加载JAR并打开这个JAR文件
    protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                //调用URLClaassLoader的私有方法
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            //如果类加载不到,会出现这个异常,开发也经常会遇到
            throw new ClassNotFoundException(name);
        }
        return result;
    }

找到之后就能读取字节码文件,并进行详细的类定义,定义完成之后,会被JVM放在元数据区或者永久代,包括字节码中的详细的属性信息,方法信息等

    /*
     * Defines a Class using the class bytes obtained from the specified
     * Resource. The resulting Class must be resolved before it can be
     * used.
     */
     //这个方法就是具体加载类的实现,通过类的字节码文件进行解析和加载
    private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
        if (i != -1) {
            //这里是一个全路径名(如com.xx.xx.Person)
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            //由于这个资源可能是个包,所以先进行包的定义
            //先定义包的逻辑其实也是通过ClassLoader类中的方法定义的
            Manifest man = res.getManifest();
            definePackageInternal(pkgname, man, url);
        }
        // Now read the class bytes and define the class
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
             //这里的代码应该跟之前的版本不太一样,因为使用了NIO
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            //JVM加载类性能的探针统计,
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            //这里最终会调用到ClassLoader类中的方法,进行具体的定义
            return defineClass(name, bb, cs);
        } else {
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            //这里最终会调用到ClassLoader类中的方法,进行具体的定义
            return defineClass(name, b, 0, b.length, cs);
        }
    }

URLClassLoader总结:

  1. java类加载机制的JVM底层源码实现,关键方法解读
  2. java类加载机制中的双亲委派模型的原貌是啥
  3. java类加载机制中的双亲委派模型中有哪些类
  4. 自定义类加载器怎么实现,怎么用,然后满足自己的需求
  5. java类加载机制中的类加载过程
  6. java类加载机制在整个JVM类加载,使用,初始化流程中的过程。
上一篇 下一篇

猜你喜欢

热点阅读