美文网首页JDK源码架构解读
JavaClassLoader源码分析(下)

JavaClassLoader源码分析(下)

作者: spring_coderman | 来源:发表于2019-12-27 15:08 被阅读0次
    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类加载,使用,初始化流程中的过程。

    相关文章

      网友评论

        本文标题:JavaClassLoader源码分析(下)

        本文链接:https://www.haomeiwen.com/subject/cirdoctx.html