Java类加载器与双亲委派模型

       虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部去实现。实现这个动作的代码模块称为类加载器

       类加载器虽然用于实现类的加载动作,但它在java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性。比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。

一、类加载器的作用

         java程序在运行的时候,JVM通过类加载机制(ClassLoader)把class文件加载到内存中,只有class文件被载入内存,才能被其他class引用,使程序正确运行起来。

二、类加载器的分类

        java中ClassLoader的分类有三种:启动类加载器扩展类加载器应用程序类加载器

  • 启动类加载器(Bootstrap ClassLoader):由C++编写,由JVM启动,启动类加载器负责加载java基础类,对应的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等。启动类加载器无法被java程序直接引用,在编写自定义类加载器时,如果需要把请求委派给引导类加载器,直接使用null代替。
  • 扩展类加载器(Extension ClassLoader):这个加载器负责加载JAVA_HOME\lib\ext目录下中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序没有自定义过自己的类加载器,一般这个就是程序中默认的类加载器

三、双亲委派模型

        双亲委派模型工作过程:如果一个类加载器收到类加载请求,它首先不会自己亲自去尝试加载这个类,而是把这个请求委派给父类加载器去完成。每个加载器都是如此,因此所有的请求最终都应该传送到顶层的启动类加载器中,只有当父加载器在自己搜索范围内找不到指定的类时(ClassNotFoundException),子加载器才会尝试自己去加载

四、认识ClassLoader类加载器代码

      1.loadClass()

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
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) {
                        //父加载器不为空则调用父加载器的loadClass()方法

                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空,调用BootstrapClassLoader(启动类加载器)

                        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.
                    //父加载器没有找到,就调用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();
                }
            }
            //调用resolveClass()
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

loadClass()函数即实现了双亲委派模型,大致过程如下:

  1. 首先检查指定全限定名的类是否已经加载过。如果加载了,就不需要再次加载,直接返回。
  2. 如果此类没有被加载,那么判断是否有类加载器;如果有,则由父加载器加载(调用parent.loadClass(name,false)方法)或者调用Bootstrap类加载器加载。
  3. 如果父加载器及Bootstrap类加载器都没有找到指定的类,那么就调用当前类加载器的findClass()方法来完成加载。

所以,如果自定义类加载器,必须重写findClass()方法。

      2.findClass()

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

      ClassLoader类中的findClass函数默认是直接抛出异常。在loadClass中提到,在父加载器无法加载类的时候,就会调用自定义的类加载器中的findClass()函数,因此必须要在loadClass这个函数里实现将一个指定类名称转换为Class对象

      java提供了defineClass()方法可以把一个字节数组转换为Class对象。

     3.defineClass()

    @Deprecated
    protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(null, b, off, len, null);
    }

       defineClass()将一个字节数组转为Class对象,这个字节数组是class文件读取后的字节数组,如果class文件是加密过的,则需要解密后作为参数传入defineClass函数。

五、自定义类加载器

  • 新建一个类继承java.lang.ClassLoader,重写findClass()方法

  • 将class字节码数组转换为Class类的实例。使用defineClass()方法

  • 调用loadClass()方法

   1.自定义类加载器

          继承ClassLoader,重写findClass方法,使用defineClass将class文件字节数组转换Class类实例。

package com.simon.loader;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * 新建自定义加载器 继承ClassLoader
 */
public class MyNewClassLoader extends ClassLoader {
    private String classPath;

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

    /**
     * 重写findClass方法
     * @param name  全限定类名的类
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            //将全限定名的class类文件读取为字节流数组
            byte[] data = loadByte(name);
            //使用defineClass方法将字节流数组作为Class对象返回
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }

    }
    /**
     * 将全限定名的class类文件读取为字节流数组
     */
    private byte[] loadByte(String name) throws IOException {
        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;

    }
}

   2.定义需要自定义类加载器加载的class文件

package com.simon.classloader;

public class Log {
    public void hello() {
        System.out.println(getClass().getClassLoader().getClass());
    }
}

          将Log.java拷贝到单独的文件夹中

        编译Log.java

  3.启动类使用自定义类加载class文件

       调用自定义的loadClass方法

package com.simon.loader;

import com.simon.classloader.Main;

import java.lang.reflect.Method;

public class Application {
    public static void main(String args[]) throws Exception {
        MyNewClassLoader classLoader = new MyNewClassLoader("D:\\test");
        Class clazz = classLoader.loadClass("com.simon.classloader.Log");
        Object obj = clazz.newInstance();
        Method helloMethod = clazz.getDeclaredMethod("hello", null);
        helloMethod.invoke(obj, null);
    }
}

  4.执行结果

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值