浅析java类加载源码及测试

浅析java类加载源码及测试

时间:20180225


类加载ClassLoader的源码中最重要的是ClassLoader方法loadClass方法findClass方法defineClass方法,至于其他的方法不做解释。

// -- Class --
//通过name(二进制字节码)加载类,并按双亲委派的算法去执行
//findLoadedClass在已经加载的类中去寻找
//任何加载器都有父加载器
//如果某个加载器的父加载器为空(NULL),则父加载器为启动加载器(Bootstrap ClassLoader,虚拟机默认的加载器)
//如果要自己实现加载器,需要重载findClass()方法
//没有找到类时throws  ClassNotFoundException异常
     
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //1. 检查当前类是否已经被加载
            Class<?> c = findLoadedClass(name);
            //2.如果当前类没有被加载过
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //3.并且当前加载器的父加载器不为空,即不为启动加载器
                    if (parent != null) {
                        //4.1 循环委托给自己的父加载器(Ext或者App)
                        c = parent.loadClass(name, false);
                    } else {
                         //4.2 循环委托给自己的父加载器(Bootstrap )
                         //返回通过BootStrap类加载器加载得到的类
                        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;
        }
    }

    //返回通过BootStrap类加载器加载得到的类
    private Class<?> findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;
        return findBootstrapClass(name);
    }

代码解释:按照包含关系(非继承关系)

  • 自定义加载器--->父加载器(自定义启动器or应用加载器App ClassLoader)--->父加载器(Ext ClassLoader)--->父加载器(启动加载器Bootstrap ClassLoader)。
  • 代码中利用递归不停查找类的加载器的父加载器。直到最终找到的父加载器为NULL也就是为启动加载器时。
  • Bootstrap ClassLoader加载器中调用findBootstrapClassOrNull()方法首先查找缓存,如果没有找到的话,就去找自己规定的路径下,也就是sun.mic.boot.class下面的路径,找到就返回,没有找到,就让子加载器自己去找(递归实现)。
  • ExtClassLoader查找不成功,App ClassLoader就自己去查找。找到就返回。如果没有找到就让子加载器去找。如果子加载器(自定义加载器没有找到)?就会抛出异常(hrows ClassNotFoundException异常)。
  • 上面代码及解释中。说明了双亲委派的加载流程。我们可以发现委派是从下向上。然后具体查找过程却是自上而下。如图解中。

图解双亲委派模型

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

/**
     * Converts an array of bytes into an instance of class <tt>Class</tt>,
     * with an optional <tt>ProtectionDomain</tt>.  If the domain is
     * <tt>null</tt>, then a default domain will be assigned to the class as
     * specified in the documentation for {@link #defineClass(String, byte[],
     * int, int)}.  Before the class can be used it must be resolved.
     *
     * <p> The first class defined in a package determines the exact set of
     * certificates that all subsequent classes defined in that package must
     * contain.  The set of certificates for a class is obtained from the
     * {@link java.security.CodeSource <tt>CodeSource</tt>} within the
     * <tt>ProtectionDomain</tt> of the class.  Any classes added to that
     * package must contain the same set of certificates or a
     * <tt>SecurityException</tt> will be thrown.  Note that if
     * <tt>name</tt> is <tt>null</tt>, this check is not performed.
     * You should always pass in the <a href="#name">binary name</a> of the
     * class you are defining as well as the bytes.  This ensures that the
     * class you are defining is indeed the class you think it is.
     *
     * <p> The specified <tt>name</tt> cannot begin with "<tt>java.</tt>", since
     * all classes in the "<tt>java.*</tt> packages can only be defined by the
     * bootstrap class loader.  If <tt>name</tt> is not <tt>null</tt>, it
     * must be equal to the <a href="#name">binary name</a> of the class
     * specified by the byte array "<tt>b</tt>", otherwise a {@link
     * NoClassDefFoundError <tt>NoClassDefFoundError</tt>} will be thrown. </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
     *
     * @param  protectionDomain
     *         The ProtectionDomain of the class
     *
     * @return  The <tt>Class</tt> object created from the data,
     *          and optional <tt>ProtectionDomain</tt>.
     *
     * @throws  ClassFormatError
     *          If the data did not contain a valid class
     *
     * @throws  NoClassDefFoundError
     *          If <tt>name</tt> is not equal to the <a href="#name">binary
     *          name</a> of the class specified by <tt>b</tt>
     *
     * @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, or if <tt>name</tt> begins with
     *          "<tt>java.</tt>".
     */
    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

三、举例

  • 例子1

package com.test.jvm;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
    private String name;//加载器名称
    private String path;//加载器名称

    //构造函数
    public MyClassLoader(String name , String path) {
        super();//让系统类加载器为该类父加载器
        this.name = name;
        this.path = path;
    }
    
    //构造函数,制定父加载器
    public MyClassLoader(ClassLoader parent, String name, String path) {
        super(parent);//显示的指定父加载器
        this.name=name;
        this.path=path;
    }
    /**
     * 加载我们定义的类,通过我们定义的ClassLoader
     */
    @Override
    protected Class<?> findClass(String name)throws ClassNotFoundException{
        byte[] data = null;
        Class<?> C = null;
            try {
                data = readClassFileToByteArray(name);
                 C  = this.defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return  C;
    }
    //如果不重写 ToString 结果是 Demo :com.test.jvm.MyClassLoader@55f96302
    //为name(全限定名\类名)对应类数据所在的地址,toString的作用是将地址转换为类名
    //即加上下面三行代码后返回结果变为:Demo :TanShen
    @Override
    public String toString() {
        return this.name;
    }
    /**
     *获取硬盘.class文件中的字节码数据,将数据存储与数组中
     *.class文件由.java文件经过cmd执行javc生成
     *1.处理传入的name(com.test.jvm.Demo或者Demo)
     * @param name
     * @return
     * @throws Exception
     */
    private byte[] readClassFileToByteArray(String name) throws Exception {
        name = name.replaceAll("\\.","/");
        String filePath = this.path + name + ".class";
        File file = new File(filePath);
        
        InputStream is = null;
        byte[] returnData = null;
        
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        
        try {
            is = new FileInputStream(file);
            int tmp = 0;
            while((tmp = is.read()) != -1) {
                os.write(tmp);
            }
            returnData = os.toByteArray();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            is.close();
            os.close();
        }
        
        return returnData;
    }   
}
package com.test.jvm;

public class TestDemo {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //new一个自定义的类加载器;并在N:/tmp/ 目录下放入Demo.java文件
        MyClassLoader loader = new MyClassLoader("TanShen","N:/tmp/");
        System.out.println("当前加载器: "+loader);
        System.out.println("loader父加载器: "+loader.getParent());
        System.out.println("loader父父加载器: "+loader.getParent().getParent());
        System.out.println("loader父父父加载器: "+loader.getParent().getParent().getParent());
        Class<?> c = loader.loadClass("Demo");
        c.newInstance();
    }
}

Demo.java中代码(别忘记cmd中执行 javac Demo,进行编译生产Demo.class)

public class Demo{
    public Demo(){
        System.out.println("Demo :" + this.getClass().getClassLoader());
    }
}

以上执行结果:
当前加载器: TanShen
loader父加载器: sun.misc.Launcher$AppClassLoader@4e25154f
loader父父加载器: sun.misc.Launcher$ExtClassLoader@55f96302
loader父父父加载器: null
Demo :TanShen

图解分析自定义类加载器进行类的加载过程
  • 例子2

MyClassLoader.java无变化

在eclipse下新建一个Demo.java 如下:

package com.test.jvm;

public class Demo {
    public Demo() {
        //调用本类的 类加载器
        System.out.println("A Demo : " + this.getClass().getClassLoader());
    }
}
package com.test.jvm;

public class TestDemo {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //new一个自定义的类加载器
        MyClassLoader loader = new MyClassLoader("TanShen","N:/tmp/");
        System.out.println("当前加载器: "+loader);
        System.out.println("loader父加载器: "+loader.getParent());
        System.out.println("loader父父加载器: "+loader.getParent().getParent());
        System.out.println("loader父父父加载器: "+loader.getParent().getParent().getParent());
        Class<?> c = loader.loadClass("com.test.jvm.Demo");
        c.newInstance();
    }
}

本地Demo.java中代码,并将Demo.java放入N:\tmp\com\test\jvm路径下(别忘记cmd中执行 javac Demo)

package com.test.jvm;
public class Demo{
    public Demo(){
                //调用本类的 类加载器
        System.out.println("Demo :" + this.getClass().getClassLoader());
    }
}

eclipse目录

目录

eclipse执行结果:
当前加载器: TanShen
loader父加载器: sun.misc.Launcher$AppClassLoader@4e25154f
loader父父加载器: sun.misc.Launcher$ExtClassLoader@55f96302
loader父父父加载器: null
A Demo : sun.misc.Launcher$AppClassLoader@4e25154f

图解分析自定义类加载器进行类的加载过程
  • 例子3

其他不变仅仅TestDemo进行改变

package com.test.jvm;

public class TestDemo {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //new一个自定义的类加载器
        MyClassLoader tanShenloader = new MyClassLoader("TanShen","N:/tmp/");
        MyClassLoader wuKongloader = new MyClassLoader(tanShenloader,"WuKong","N:/tmp/");
        System.out.println("当前加载器: "+wuKongloader);
        System.out.println("loader父加载器: "+wuKongloader.getParent());
        System.out.println("loader父父加载器: "+wuKongloader.getParent().getParent());
        System.out.println("loader父父父加载器: "+wuKongloader.getParent().getParent().getParent());
        System.out.println("loader父父父加载器: "+wuKongloader.getParent().getParent().getParent().getParent());
        
        Class<?> c = wuKongloader.loadClass("com.test.jvm.Demo");
        c.newInstance();
    }
}

结果:
当前加载器: WuKong
loader父加载器: TanShen
loader父父加载器: sun.misc.Launcher$AppClassLoader@4e25154f
loader父父父加载器: sun.misc.Launcher$ExtClassLoader@55f96302
loader父父父加载器: null
A Demo : sun.misc.Launcher$AppClassLoader@4e25154f

案例分析:
当前自定义的加载器为WuKong,并指定其父加载器为:TanShen(也为自定义加载器),根据双亲委派模型,会继续寻找TanShen父加载器AppClassLoader;继而ExtClassLoader,最总 null==BootStrapClassLoader;
最后是
A Demo : sun.misc.Launcher$AppClassLoader@4e25154f去执行加载类,为什么?因为向下查找类的过程,AppClassLoader在缓存中找到了类Demo(就是java工程目录下的文件)。

  • 例子4

其他不变仅仅TestDemo进行改变

package com.test.jvm;

public class TestDemo {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //new一个自定义的类加载器
        MyClassLoader wuKongloader = new MyClassLoader(null,"WuKong","N:/tmp/");
        System.out.println("当前加载器: "+wuKongloader);
        System.out.println("loader父加载器: "+wuKongloader.getParent());
        Class<?> c = wuKongloader.loadClass("com.test.jvm.Demo");
        c.newInstance();
    }
}

结果
当前加载器: WuKong
loader父加载器: null
Demo :WuKong

图解分析

指定自定义类加载器的父加载器为Null(BootstrapClassLoader)

参考
https://www.bilibili.com/video/av17748750/
http://blog.csdn.net/briblue/article/details/54973413
搜索“探索JVM底层奥秘ClassLoader源码分析与案例讲解“

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

推荐阅读更多精彩内容