类加载器源码、双亲委派、自定义类加载器详解


jdk的类加载器

查看一个类的类加载器:

 ClassLoader classLoader = boy.class.getClassLoader();

查看父加载器:

ClassLoader classLoader = boy.class.getClassLoader().getParent();

如果父加载器是根加载器,打印的是null
例如

ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassloader = appClassLoader.getParent();
        ClassLoader bootstrapLoader = extClassloader.getParent();
        System.out.println("the bootstrapLoader : " + bootstrapLoader);
        System.out.println("the extClassloader : " + extClassloader);
        System.out.println("the appClassLoader : " + appClassLoader);

查看classLoader 的loadClass源码:

public abstract class ClassLoader {
//加载具有指定二进制名称的类。此方法的默认实现按以下顺序搜索类:调用 findLoadedClass(String) 以检查该类是否已加载。在父类加载器上调用 loadClass 方法。如果 parent 为 null,则使用虚拟机内置的类加载器。调用 findClass(String) 方法来查找 class
private final ClassLoader parent;//parent不是继承关系,而是作为成员变量
 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //首先检查是否已经加载过
            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
                    //从非空父类加载器
                }

                if (c == null) {
                    // 如果仍未找到,则调用 findClass 以查找该类
                    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;
        }
    }

loadClass的类加载过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证(校验字节码文件)、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM 对类进行初始化,包括:

1 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;

2 如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。从JDK 1.2 开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了Java 平台的安全性,在该机制中,JVM 自带的Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向Java 程序提供对Bootstrap 的引用。

注意,主类在运行过程中如果使用到其它类,会逐步加载这些类。
jar包或war包里的类不是一次性全部加载的,是使用到时才加载。

双亲委派

当“应用程序类加载器"接到一个加载任务时
(1)先搜素内存中是否已经加載过了,如果加载 过了,就可以找到对应的C1ass对象,那么就不加载了
(2)如 果没有找到,把这个任务先提交给 parent",父加载器接到任务时,也是重复(1)(2)
3)直到传给了根加载器,如果根加载器可以加载,就完成了,如果不能加戟,往回传,依次每 个加載器尝试在自己负责的路径下搜素,如果找到就直接返回Class对象,如果一直回传到“应用程序类加载器”,还是没有找到就会报ClassnotFoundException

为什么用这种机制:

为了安全,防止你写一个核心类。
每一个加载器只负责自己路径下的东西。

类加载器的作用
1、最主要的作用 :加载类
2、辅助的作用:可以用它 来加载“类路径下”的资源文件
例如:bin中src下文件ー->bin目录下Properties.Propertie s类表示了一个持久的属性集, Properties可保存在流中或从中加载属性列表中每个键
举个例子,ClassLoader 加载的class 文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。

而一些来源的class 文件是不可靠的,比如我可以自定义一个java.lang.Integer 类来覆盖jdk 中默认的Integer

例如下面这样:

 package java.lang;
 public class Integer {
 public Integer(int value) {
System.exit(0);
}
}

初始化这个Integer 的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer 类永远不会被调用,以为委托BootStrapClassLoader 加载后会加载JDK 中的Integer 类而不会加载自定义的这个

自定义类加载器

public class MyClassLoader extends ClassLoader{
   private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }
  private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

    }

    public static void main(String args[]) throws Exception {
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        //D盘创建 test/com/test/jvm 几级目录,将User类的复制类User1.class丢入该目录
        Class clazz = classLoader.loadClass("com.test.jvm.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

打破双亲委派

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;

        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        /**
         * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */
        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) {
                    // 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.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }

    public static void main(String args[]) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
        Class clazz = classLoader.loadClass("java.lang.String");
        Object obj = clazz.newInstance();
        Method method= clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

运行结果:
java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值