前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ClassLoader 源码详解

ClassLoader 源码详解

作者头像
tomas家的小拨浪鼓
发布2019-08-07 15:28:57
7670
发布2019-08-07 15:28:57
举报
文章被收录于专栏:木木玲木木玲

API 文档详解

ClassLoader是一个类加载器对象,负责去加载类。ClassLoader是一个抽象对象。给定了一个类的“二进制名称”,一个类加载器需要尝试去定位或者生成一个数据,该数据构成了一个定义的类。一个典型的策略就是转换名字(即,二进制名字)成一个文件名,然后从文件系统读取这个文件名包含的“字节码文件”。 注意,这里有两种方式来通过一个“二进制名称”来加载一个类: ① 定位 也就是说,表示这个类的数据已经存在了,类加载器去定位到这个存储的数据进行加载即可。比如,java.lang.String就是在rt.jar中存储的了,可以直接定位到。 ② 生成 一些在java代码中动态生成的类,而这些类的数据就是在运行期时由类加载器去生成的。比如,动态代理。

“二进制名称”:任意一个类名被提供作为ClassLoader方法的字符串参数,这个字符串形式的类名字必须是一个二进制名称,这个二进制名字是由java语言规范定义的。 有效类名字的示例包括:

代码语言:javascript
复制
"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"

"java.security.KeyStore$Builder$FileBuilder$1":KeyStore里面的内部类Builder,Builder里面的内部类FileBuilder,FileBuilder里面的“第一个”匿名内部类。

每个Class对象包含了一个定义它的ClassLoader的引用(『Class#getClassLoader()』返回一个指向ClassLoader的引用)。

数组类的Class对象不是由类加载器创建的,而是Java虚拟机在运行时根据需要所自动创建(注意,只有数组类是特殊的,其他类对象都是通过类加载器来创建的)。数组类的类加载器(即,『ArrayClass#getClassLoader()』),同它的元素类型通过『Class#getClassLoader()』返回的类加载器是一样的;如果元素类型是一个原生类型,那么数组类没有类加载器(即,『Class#getClassLoader()』返回null)。

代码语言:javascript
复制
public class MyTest15 {

    public static void main(String[] args) {

        String[][] strings = new String[2][];
        System.out.println(strings.getClass().getClassLoader());

        System.out.println("=============================");

        MyTest15[] myTest15s = new MyTest15[2];
        System.out.println(myTest15s.getClass().getClassLoader());

        System.out.println("=============================");

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());

    }
}

# 控制台
null
=============================
sum.misc.Launcher$AppClassLoader@18b4aac2
=============================
null

对于数组类而言,情况就有所不同,数组类本身不通过类加载器创建,它是由Java虚拟机在运行时直接创建的(‘数组’的父类是’Object’)。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型(Element Type,指的是数组去掉所有维度的类型)最终是要靠类加载器去创建。 如果数组的组件类型(Component Type,指的是数组去掉一个维度的类型)是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型,数组C将在加载该组件类型的类加载器的类名称空间上被标识。 如果数组的组件类型不是引用类型(例如int[]数组),Java虚拟机将会把数组C标记为与引导类加载器关联。 所以,这里。strings.getClass().getClassLoader() 和 ints.getClass().getClassLoader() 都返回 null,标签其都是通过“引导类加载器”加载的。

应用实现ClassLoader的子类为了扩展Java虚拟机动态加载类的方式。 ?这个句话说明了,自定义类加载器的核心用途。

类加载器典型情况下是可以被安全管理器所使用来去指示的一些安全域问题。 也就是,类加载器本身都会伴随着一个安全管理器的概念,来去确保类加载的过程一定是安全的。

ClassLoader类使用一个委托模型去查询类和资源。ClassLoader的每一个实例有一个相关的父加载器。当请求去寻找一个类或资源时,一个ClassLoader实例会将类或资源的查询委托给它的父加载器在它自己去尝试去寻找类或资源之前。虚拟机“内建”的类加载器叫做“启动类加载器”,它没有一个父加载器,但是它作为一个ClassLoader实例的父加载器。

支持并发加载类的类加载器被称为并行的类加载器,并且被要求通过『ClassLoader.registerAsParallelCapable』方法去注册它们自己当它们的类初始化时。注意,ClassLoader默认被注册为有并行能力的。然而,它们的子类仍然需要去注册它们自己,如果它们(即,ClassLoader的子类)是并行加载的。 在委托模式并不是严格的层次化的环境下(即,和JVM内建的委托模型不一致或冲突的情况下),类加载器是需要并行能力的,否则类加载将导致死锁,因为加载锁在类加载过程中被持续持有。(见『ClassLoader#loadClass』方法)

通常地,Java虚拟机以平台相关的方式(即,不是独立于平台的)从本地文件系统加载类。比如,在UNIX系统下,虚拟机通过环境变量“CLASSPAH”定义的目录中加载类。

然而,一些类可能不是起源于一个文件;它们可能源于其他来源,比如网络,或者它们能被一个应用构造(比如,动态代理)。『defineClass(String, byte[], int, int)』方法会将一个字节数组转换为一个Class类实例。这个新定义的类的实例能使用『Class#newInstance』方法来创建。

通过一个类加载器创建的对象的方法和构造方法可能会引用到其他类。为了确定所引用的类,Java虚拟机调用最初创建该类的类加载器的『loadClass』方法。(即,使用这个类的定义类加载器去加载所引用的类) 真正负责成功加载这个类的加载器,我们称之为“定义类加载器”。 接受类加载请求,通过调用loadClass来开启类的加载过程的加载器被称为初始类加载器。

比如,一个应用可以创建一个网络‘类加载器’,以从一个服务端下载类。简单的代码可能看起来像:

代码语言:javascript
复制
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
. . .

网络类加载器的子类必须定义『findClass』和『loadClassData』方法去从网络加载一个类。一旦它已经下载了组成类的字节,它需要使用『defineClass』方法去创建一个类实例。一个简单的实现:

代码语言:javascript
复制
class NetworkClassLoader extends ClassLoader {
           String host;
           int port;
  
           public Class findClass(String name) {
               byte[] b = loadClassData(name);
               return defineClass(name, b, 0, b.length);
           }
  
           private byte[] loadClassData(String name) {
               // load the class data from the connection
                . . .
           }
       }

重要方法讲解

  • Class#getClassLoader()
代码语言:javascript
复制
/**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     *
     * <p> If a security manager is present, and the caller's class loader is
     * not null and the caller's class loader is not the same as or an ancestor of
     * the class loader for the class whose class loader is requested, then
     * this method calls the security manager's {@code checkPermission}
     * method with a {@code RuntimePermission("getClassLoader")}
     * permission to ensure it's ok to access the class loader for the class.
     *
     * <p>If this object
     * represents a primitive type or void, null is returned.
     *
     * @return  the class loader that loaded the class or interface
     *          represented by this object.
     * @throws SecurityException
     *    if a security manager exists and its
     *    {@code checkPermission} method denies
     *    access to the class loader for the class.
     * @see java.lang.ClassLoader
     * @see SecurityManager#checkPermission
     * @see java.lang.RuntimePermission
     */
    @CallerSensitive
    public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
        }
        return cl;
    }

返回真实加载这个类/接口的加载器。一些实现可能会返回null表示“启动类加载器”。这个方法就是这样的实现,它将会返回null,如果类是被“启动类加载器”加载的话。 如果这里使用了安全管理器的话,并且”调用者的类加载器“或者”请求加载这个类的类加载器的祖先类加载器“不为空。那么这个方法就会去调用安全管理器的『checkPermission()』方法来去看是否能访问到这个类的加载器(定义类加载器)。 如果这个对象代表了一个原生类型或者void,那么会返回null。

  • getSystemClassLoader()
代码语言:javascript
复制
/**
     * Returns the system class loader for delegation.  This is the default
     * delegation parent for new <tt>ClassLoader</tt> instances, and is
     * typically the class loader used to start the application.
     *
     * <p> This method is first invoked early in the runtime's startup
     * sequence, at which point it creates the system class loader and sets it
     * as the context class loader of the invoking <tt>Thread</tt>.
     *
     * <p> The default system class loader is an implementation-dependent
     * instance of this class.
     *
     * <p> If the system property "<tt>java.system.class.loader</tt>" is defined
     * when this method is first invoked then the value of that property is
     * taken to be the name of a class that will be returned as the system
     * class loader.  The class is loaded using the default system class loader
     * and must define a public constructor that takes a single parameter of
     * type <tt>ClassLoader</tt> which is used as the delegation parent.  An
     * instance is then created using this constructor with the default system
     * class loader as the parameter.  The resulting class loader is defined
     * to be the system class loader.
     *
     * <p> If a security manager is present, and the invoker's class loader is
     * not <tt>null</tt> and the invoker's class loader is not the same as or
     * an ancestor of the system class loader, then this method invokes the
     * security manager's {@link
     * SecurityManager#checkPermission(java.security.Permission)
     * <tt>checkPermission</tt>} method with a {@link
     * RuntimePermission#RuntimePermission(String)
     * <tt>RuntimePermission("getClassLoader")</tt>} permission to verify
     * access to the system class loader.  If not, a
     * <tt>SecurityException</tt> will be thrown.  </p>
     *
     * @return  The system <tt>ClassLoader</tt> for delegation, or
     *          <tt>null</tt> if none
     *
     * @throws  SecurityException
     *          If a security manager exists and its <tt>checkPermission</tt>
     *          method doesn't allow access to the system class loader.
     *
     * @throws  IllegalStateException
     *          If invoked recursively during the construction of the class
     *          loader specified by the "<tt>java.system.class.loader</tt>"
     *          property.
     *
     * @throws  Error
     *          If the system property "<tt>java.system.class.loader</tt>"
     *          is defined but the named class could not be loaded, the
     *          provider class does not define the required constructor, or an
     *          exception is thrown by that constructor when it is invoked. The
     *          underlying cause of the error can be retrieved via the
     *          {@link Throwable#getCause()} method.
     *
     * @revised  1.4
     */
    @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

返回一个基于委托模式的系统类加载器。它是新的类加载器默认的委托父类实例,并且它是用于启动应用的典型类加载器。 首先在运行时的启动序列中调用此方法,此时它会创建系统类加载器并将其设置为调用线程的上下文类加载器。 默认的系统类加载器是与这个实现相关的一个实例。 如果当这个方法第一次被调用的时候,系统属性”java.system.class.loader”是被定义的,那么这个属性的值就会被作为系统类加载器的名字。而这个类是使用默认的系统类加载器来去加载的,并且必须定义一个public的接收单个类型为ClassLoader参数的构造方法,同时这个传入的ClassLoader会作为委托的双亲。一个实例接下来会被创建通过使用这个构造方法,同时会将默认的系统类加载器作为参数传入,而所生成的类就会被定义成’系统类加载器’。 也就是说,默认的情况下’系统类加载器’就是’AppClassLoader’,但是对于JDK来说,如果提供了”java.system.class.loader"这个系统属性,我们可以通过这个系统属性来去显示的修改“系统类加载器”,也就是说让“系统类加载器”不再是“AppClassLoader”,而是我们自定义的某个ClassLoader。

  • ClassLoader parent
代码语言:javascript
复制
// The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;

虚拟机会硬编码这个字段的偏移量,因此所有新的字段必须在它的后面。

  • Thread#getContextClassLoader()()
代码语言:javascript
复制
/**
     * Returns the context ClassLoader for this Thread. The context
     * ClassLoader is provided by the creator of the thread for use
     * by code running in this thread when loading classes and resources.
     * If not {@linkplain #setContextClassLoader set}, the default is the
     * ClassLoader context of the parent Thread. The context ClassLoader of the
     * primordial thread is typically set to the class loader used to load the
     * application.
     *
     * <p>If a security manager is present, and the invoker's class loader is not
     * {@code null} and is not the same as or an ancestor of the context class
     * loader, then this method invokes the security manager's {@link
     * SecurityManager#checkPermission(java.security.Permission) checkPermission}
     * method with a {@link RuntimePermission RuntimePermission}{@code
     * ("getClassLoader")} permission to verify that retrieval of the context
     * class loader is permitted.
     *
     * @return  the context ClassLoader for this Thread, or {@code null}
     *          indicating the system class loader (or, failing that, the
     *          bootstrap class loader)
     *
     * @throws  SecurityException
     *          if the current thread cannot get the context ClassLoader
     *
     * @since 1.2
     */
    @CallerSensitive
    public ClassLoader getContextClassLoader() {
        if (contextClassLoader == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                   Reflection.getCallerClass());
        }
        return contextClassLoader;
    }

返回这个线程的上下文类加载器。线程上下文类加载器是通过线程的创建者本身所提供的,用于在运行线程中代码的时候去加载类与资源。如果没有设置『setContextClassLoader』,那么默认的上下文类加载器就是父线程的上下文类加载器。一个原始线程的上下文类加载器典型情况下会被设置为用于加载应用的类加载器(也就是“系统类加载器”)。

  • getResource(String name)
代码语言:javascript
复制
public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

寻找给定名字的所有资源。一个资源是一些能被类代码访问的数据(如,图片、音频、文本,等),它(即,数据)是以独立于代码位置的方式进行访问的。 一个资源的名称是一个以“/”来去分割的路径名称来标识的资源。

  • ClassLoader() 构造方法
代码语言:javascript
复制
使用通过『getSystemClassLoader()』方法返回的类加载器来创建一个新的类加载器。
getSystemClassLoader()作为它的父类加载器。
也就是说,默认情况下,当使用无参构造方法创建新的类加载器时我们所自定义的类加载器的父类加载器就是系统类加载器。
  • findClass(String name)
代码语言:javascript
复制
/**
     * Finds the class with the specified <a href="#name">binary name</a>.
     * This method should be overridden by class loader implementations that
     * follow the delegation model for loading classes, and will be invoked by
     * the {@link #loadClass <tt>loadClass</tt>} method after checking the
     * parent class loader for the requested class.  The default implementation
     * throws a <tt>ClassNotFoundException</tt>.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     *
     * @since  1.2
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

寻找指定“二进制名字”的类。这个方法应该被类加载器的实现所重写,这个方法必须被类加载器所实现,且该类加载器应该遵循委托模型来去加载指定的类,这个方法会在父类加载器检测完请求类的加载后被『loadClass』方法所调用。该方法的默认实现会抛出一个“ClassNotFoundException”异常。

  • defineClass(String name, byte[] b, int off, int len)
代码语言:javascript
复制
/**
     * Converts an array of bytes into an instance of class <tt>Class</tt>.
     * Before the <tt>Class</tt> can be used it must be resolved.
     *
     * <p> This method assigns a default {@link java.security.ProtectionDomain
     * <tt>ProtectionDomain</tt>} to the newly defined class.  The
     * <tt>ProtectionDomain</tt> is effectively granted the same set of
     * permissions returned when {@link
     * java.security.Policy#getPermissions(java.security.CodeSource)
     * <tt>Policy.getPolicy().getPermissions(new CodeSource(null, null))</tt>}
     * is invoked.  The default domain is created on the first invocation of
     * {@link #defineClass(String, byte[], int, int) <tt>defineClass</tt>},
     * and re-used on subsequent invocations.
     *
     * <p> To assign a specific <tt>ProtectionDomain</tt> to the class, use
     * the {@link #defineClass(String, byte[], int, int,
     * java.security.ProtectionDomain) <tt>defineClass</tt>} method that takes a
     * <tt>ProtectionDomain</tt> as one of its arguments.  </p>
     *
     * @param  name
     *         The expected <a href="#name">binary name</a> of the class, or
     *         <tt>null</tt> if not known
     *
     * @param  b
     *         The bytes that make up the class data.  The bytes in positions
     *         <tt>off</tt> through <tt>off+len-1</tt> should have the format
     *         of a valid class file as defined by
     *         <cite>The Java&trade; Virtual Machine Specification</cite>.
     *
     * @param  off
     *         The start offset in <tt>b</tt> of the class data
     *
     * @param  len
     *         The length of the class data
     *
     * @return  The <tt>Class</tt> object that was created from the specified
     *          class data.
     *
     * @throws  ClassFormatError
     *          If the data did not contain a valid class
     *
     * @throws  IndexOutOfBoundsException
     *          If either <tt>off</tt> or <tt>len</tt> is negative, or if
     *          <tt>off+len</tt> is greater than <tt>b.length</tt>.
     *
     * @throws  SecurityException
     *          If an attempt is made to add this class to a package that
     *          contains classes that were signed by a different set of
     *          certificates than this class (which is unsigned), or if
     *          <tt>name</tt> begins with "<tt>java.</tt>".
     *
     * @see  #loadClass(String, boolean)
     * @see  #resolveClass(Class)
     * @see  java.security.CodeSource
     * @see  java.security.SecureClassLoader
     *
     * @since  1.1
     */
    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }

将一个字节数组转换成一个Class类的实例。在Class能被使用之前它必须要被解析(该“解析”就是类加载过程中的“连接”的第三阶段)。 这个方法会分配一个默认的ProtectionDomain给新定义的类(ProtectionDomain是为了确保我们所返回来的Class的一切信息都是正确的。比如,我们去加载一个类,我们要确保这个类跟它相同包下其他的类用相同的包名。)。这个默认的domain是在第一次调用『defineClass』的时候被创建,并且在随后的调用当中可以被复用。 要想给class指定一个特定的ProtectionDomain,可以使用defineClass的另一个重载方法(『protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)』) SecurityException(运行时异常):如果尝试做出添加这个类到一个包中,该包中包含的类使用了不同这个类的证书签名;或者,如果二进制的名称以“java.”开头。

  • loadClass(String name)
代码语言:javascript
复制
/**
     * Loads the class with the specified <a href="#name">binary name</a>.
     * This method searches for classes in the same manner as the {@link
     * #loadClass(String, boolean)} method.  It is invoked by the Java virtual
     * machine to resolve class references.  Invoking this method is equivalent
     * to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
     * false)</tt>}.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class was not found
     */
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

加载使用指定二进制名字的类。这个方法搜索类的方式是与『loadClass(String, boolean)』方法一样的。

  • loadClass(String name, boolean resolve)
代码语言:javascript
复制
/**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

加载使用指定二进制名字的类。这个方法默认的实现,会按照如下的顺序来查找这个类: ① 调用『findLoadedClass(String)』方法来检查这个类是否已经被加载了。 ② 调用父类加载器的『loadClass』方法。如果父类加载器是null,那么虚拟机内建的类加载器(bootstrap class loader)会被使用。 ③ 调用『findClass(String)』方法来寻找类。 如果类在上述步骤中被找到了,并且“解析”标志位true,那么这个方法接下来会在结果类对象上调用『resolveClass(Class)』。ClassLoader类的子类被鼓励去重写『findClass(String)』,而非当前方法。 除非被重写,否则这个方法会同步『getClassLoadingLock』方法的结果在整个类加载的过程期间(以确保每个类只会被加载一次)。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019.08.05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • API 文档详解
  • 重要方法讲解
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档