ClassLoader源码解析

1.简介

A class loader is an object that is responsible for loading classes. 
The class ClassLoader is an abstract class. Given the binary name of 
a class, a class loader should attempt to locate or generate data that 
constitutes a definition for the class. A typical strategy is to transform 
the name into a file name and then read a "class file" of that name 
from a file system.
Every Class object contains a reference to the ClassLoader that 
defined it.

类加载器负责加载类。给定类的二进制名,类加载器会尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名字的类文件。

每个Class对象都包含定义它的ClassLoader。

Class objects for array classes are not created by class loaders, but 
are created automatically as required by the Java runtime. The class 
loader for an array class, as returned by Class.getClassLoader() is 
the same as the class loader for its element type; if the element type 
is a primitive type, then the array class has no class loader.

数组Class不是由类加载器创建的,而是由JVM自动创建的。Class.getClassLoader()返回的与其元素类型的类加载器一样;如果是基本类型,则数组类没有类加载器。

示例:

        int[] iArray = new int[10];
        System.out.println(iArray.getClass().getClassLoader());

        Integer[] intArray = new Integer[10];
        System.out.println(intArray.getClass().getClassLoader());

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

结果:

null
null
null
Applications implement subclasses of ClassLoader in order to extend 
the manner in which the Java virtual machine dynamically loads 
classes.

Class loaders may typically be used by security managers to indicate 
security domains.

应用程序可以通过继承ClassLoader扩展JVM动态加载类的方式。

安全管理器可以通过使用类加载器来显示安全域。

The ClassLoader class uses a delegation model to search for 
classes and resources. Each instance of ClassLoader has an 
associated parent class loader. When requested to find a class or 
resource, a ClassLoader instance will delegate the search for the 
class or resource to its parent class loader before attempting to find 
the class or resource itself. The virtual machine's built-in class loader, 
called the "bootstrap class loader", does not itself have a parent but 
may serve as the parent of a ClassLoader instance.

ClassLoader类使用委派模型来搜索类和资源,其每个实例都有一个关联的父类加载器。当请求查找类或资源时,会首先委派给父加载器,然后才是自己尝试加载。JVM内置的"bootstrap class loader"没有父亲,但可以作为ClassLoader实例的父亲。

Class loaders that support concurrent loading of classes are known 
as parallel capable class loaders and are required to register 
themselves at their class initialization time by invoking the 
ClassLoader.registerAsParallelCapable method. Note that the 
ClassLoader class is registered as parallel capable by default. 
However, its subclasses still need to register themselves if they are 
parallel capable. 
In environments in which the delegation model is not strictly 
hierarchical, class loaders need to be parallel capable, otherwise 
class loading can lead to deadlocks because the loader lock is held 
for the duration of the class loading process (see loadClass 
methods).

支持并发加载类的类加载器称为并发类加载器,需要通过调用ClassLoader.registerAdParallelCapable方法在类初始化时注册自己。ClassLoder类默认注册为并行。但是,其子类仍然需要注册自己。

在委派模型不是严格分层的环境中,类加载器需要具有并行能力,否则会导致死锁。因为在类加载过程中一直持有loader lock (参见loadClass方法)

Normally, the Java virtual machine loads classes from the local file 
system in a platform-dependent manner. For example, on UNIX 
systems, the virtual machine loads classes from the directory defined 
by the CLASSPATH environment variable.

通常,JVM以与平台相关的方式从本地文件系统加载类。例如,在UNIX系统上,JVM从CLASSPATH环境变量定义的目录中加载类。

However, some classes may not originate from a file; they may 
originate from other sources, such as the network, or they could be 
constructed by an application. The method defineClass converts an 
array of bytes into an instance of class Class. Instances of this newly 
defined class can be created using Class.newInstance.

但是,某些类可能不是源自文件,它们的来源可能是网络,或是由应用程序创建。方法defineClass将字节数组转换为Class类实例。可以使用Class.newInstance创建此类的实例。

The methods and constructors of objects created by a class loader 
may reference other classes. To determine the class(es) referred to, 
the Java virtual machine invokes the loadClass method of the class 
loader that originally created the class.

类加载器创建的对象和构造器可能会引用其他类。要确定所引用的类,JVM将调用该类类加载器的loadClass方法。

   ClassLoader loader = new NetworkClassLoader(host, port);
   Object main = loader.loadClass("Main", true).newInstance();
        . . .
     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
              . . .
         }
     }

NetworkClassLoader的loadClassData方法需要从网络下载类的字节数据。方法defineClass将字节数组转换为Class类实例。

二进制类名需要符合规范:

   "java.lang.String"
   "javax.swing.JSpinner$DefaultEditor"
   "java.security.KeyStore$Builder$FileBuilder$1"
   "java.net.URLClassLoader$3$1"

2.loadClass

    /**
     * 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);
    }
    /**
     * 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;
        }
    }

加载指定二进制名binary name的类,方法的默认实现按照如下的顺序搜索类:

  • step1.调用findLoadedClass(String)检查该类是否已经加载
  • step2.调用父加载器的loadClass(String),如果父亲是null,则调用JVM内置的加载器
  • step3.调用findClass(String)查找类

如果通过上面的步骤找到了类,并且resolve标识是true,则调用resolveClass(Class)。

ClassLoader子类建议重写findClass而不是该方法(loadClass)。

除非重写,该方法在整个类加载过程中会进行同步synchronized (getClassLoadingLock(name))。

下图来源【JVM】深度分析Java的ClassLoader机制(源码级别)

3.findLoadedClass

    /**
     * Returns the class with the given <a href="#name">binary name</a> if this
     * loader has been recorded by the Java virtual machine as an initiating
     * loader of a class with that <a href="#name">binary name</a>.  Otherwise
     * <tt>null</tt> is returned.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @return  The <tt>Class</tt> object, or <tt>null</tt> if the class has
     *          not been loaded
     *
     * @since  1.1
     */
    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

    private native final Class<?> findLoadedClass0(String name);

如果该加载器被JVM标记为该类的初始加载器,则返回该类。否则返回null。

4.resolveClass

    /**
     * Links the specified class.  This (misleadingly named) method may be
     * used by a class loader to link a class.  If the class <tt>c</tt> has
     * already been linked, then this method simply returns. Otherwise, the
     * class is linked as described in the "Execution" chapter of
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @param  c
     *         The class to link
     *
     * @throws  NullPointerException
     *          If <tt>c</tt> is <tt>null</tt>.
     *
     * @see  #defineClass(String, byte[], int, int)
     */
    protected final void resolveClass(Class<?> c) {
        resolveClass0(c);
    }

    private native void resolveClass0(Class<?> c);

链接指定类。如果该类已经被链接过,则直接返回,否则按照Java语言规范中"Chapter12. Execution"章描述的那样进行链接。

类被加载后,需要在初始化前被链接。链接涉及校验、准备和(可选的)解析。

  • 校验会检查加载的类是否是良构的,是否具有正确的符号表。校验还会检查代码是否遵循Java编程语言和JVM的语义要求。
  • 准备工作涉及静态存储的内存分配,以及所有在JVM需要使用的数据结构的内存分配,例如方法表
  • 解析:检查对其他类和接口的符号引用的过程,通过加载相关类和接口,来检查这些引用是正确。

5.getClassLoadingLock(name)

    /**
     * Returns the lock object for class loading operations.
     * For backward compatibility, the default implementation of this method
     * behaves as follows. If this ClassLoader object is registered as
     * parallel capable, the method returns a dedicated object associated
     * with the specified class name. Otherwise, the method returns this
     * ClassLoader object.
     *
     * @param  className
     *         The name of the to-be-loaded class
     *
     * @return the lock for class loading operations
     *
     * @throws NullPointerException
     *         If registered as parallel capable and <tt>className</tt> is null
     *
     * @see #loadClass(String, boolean)
     *
     * @since  1.7
     */
    protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

返回类加载操作的锁对象。
为了向后兼容,此方法默认实现如下:

  • 如果此ClassLoader对象注册了并行功能,则此方法会返回与指定类名关联的专用对象
  • 否则返回此ClassLoader对象。
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

示例:

public class TestClassLoader {
    public static final MyClassLoader WZ_LOADER = new MyClassLoader(null,"E:\\IdeaProjects\\javabasics\\target\\classes\\","wz");

    public static void main(String[] args) throws Exception {
        new Thread() {
            @Override
            public void run() {

                Class<?> c2 = null;
                try {
                    c2 = WZ_LOADER.loadClass("com.enjoy.learn.core.jvm.Test");
                    c2.newInstance();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        Class<?> c = WZ_LOADER.loadClass("com.enjoy.learn.core.jvm.Test");
        c.newInstance();
    }
}

主线程和thread线程在WZ_LOADER.loadClass处会竞争锁。

修改MyClassLoader,加入:

    static {
        ClassLoader.registerAsParallelCapable();
    }

并发时,会不断以(类名,new Object())更新parallelLockMap,如果指定的类名项已存在,则返回之前的Object。对于加载同一个类,还是会阻塞,但是对于该类加载器加载其他不同类,就不会阻塞。因此,并发性提高了。

6.示例

6.1 同一类加载器不同实例加载同一个类

        MyClassLoader loader = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
        Class<?> c = loader.loadClass("com.enjoy.learn.core.jvm.Test");
        c.newInstance();

        MyClassLoader loader2 = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
        Class<?>  c2 = loader2.loadClass("com.enjoy.learn.core.jvm.Test");
        c2.newInstance();

        System.out.println(c == c2);
        System.out.println(c.equals(c2));

结果:

false
false

Class的equals调用的是Object,是用==判断两个对象是否相等。

这种隔离特性可以用来解决钻石依赖问题:



maven 会从多个冲突的版本中选择一个来使用,如果不同的版本之间兼容性很糟糕,那么程序将无法正常编译运行。Maven 这种形式叫「扁平化」依赖管理。依赖于虚拟机的默认懒惰加载策略,运行过程中如果没有显示使用定制的 ClassLoader,那么从头到尾都是在使用 AppClassLoader,而不同版本的同名类必须使用不同的 ClassLoader 加载,所以 Maven 不能完美解决钻石依赖。

ClassLoader 固然可以解决依赖冲突问题,不过它也限制了不同软件包的操作界面必须使用反射或接口的方式进行动态调用。

SOFAArk官网
可以参考SOFAArk Project,SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,由蚂蚁金服公司开源贡献;主要提供类隔离和应用(模块)动态部署能力;基于 Fat Jar 技术,可以将多个应用(模块)打包成一个自包含可运行的 Fat Jar,应用既可以是简单的单模块 Java 应用也可以是 Spring Boot/SOFABoot 应用。

同一个类加载器加载的类是相等的:

        MyClassLoader loader = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
        Class<?> c = loader.loadClass("com.enjoy.learn.core.jvm.Test");
        c.newInstance();

        Class<?>  c2 = loader.loadClass("com.enjoy.learn.core.jvm.Test");
        c2.newInstance();

        System.out.println(c == c2);
true

6.2 自定义类加载器

public class MyClassLoader extends ClassLoader {
    private String path;
    private String name;

    public MyClassLoader(ClassLoader parent, String path, String name) {
        super(parent);
        this.path = path;
        this.name = name;
    }

    public MyClassLoader(String path, String name) {
        super();
        this.path = path;
        this.name = name;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = readClassFile2ByteArray(name);
        return this.defineClass(name, data,0, data.length);
    }

    private byte[] readClassFile2ByteArray(String name) {
        InputStream is = null;
        byte[] returnData = null;

        name = name.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
        String filePath = this.path + name + ".class";
        File file = new File(filePath);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        try {
            is = new FileInputStream(file);
            int tmp = 0;
            while ((tmp = is.read()) != -1){
                byteArrayOutputStream.write(tmp);
            }
            returnData = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(is != null) {
                    is.close();
                }
                if(byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return returnData;
    }

    @Override
    public String toString() {
        return "MyClassLoader{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class Test {
    public Test() {
        System.out.println("A ClassLoader:" + this.getClass().getClassLoader()
                + " from user");
    }
}
public class TestClassLoader {
    public static void main(String[] args) throws Exception {
        MyClassLoader loader = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
        Class<?> c = loader.loadClass("com.enjoy.learn.core.jvm.Test");

        c.newInstance();

结果:

A ClassLoader:MyClassLoader{name='wz'} from user

两个注意事项:

  • 类名binary name应该是com.enjoy.learn.core.jvm.Test,而不是Test
  • name.replaceAll("\.", Matcher.quoteReplacement(File.separator))而不是name.replaceAll("\.", File.separator)

6.3 静态变量初始化在哪里调用?

修改Test,添加静态变量:

public class Test {
    public static int a = 1;
    static {
        System.out.println(a);
    }

    public Test() {
        System.out.println("A ClassLoader:" + this.getClass().getClassLoader()
                + " from user");
    }
}

可知,<clinit>是在c.newInstance()中调用的,对静态变量进行了初始化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354