Java类加载器及双亲委派机制

类加载机制

.java文件中的代码在编译后,就会生成JVM能够识别的二进制字节流class文件,class文件中描述的各种信息,都需要加载到虚拟机中才能被运行和使用。

类加载机制,就是虚拟机把类的数据从class文件加载到内存,并对数据进行校检,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程。

类从加载到虚拟机内存开始,到卸载出内存结束,整个生命周期包括七个阶段,如下图(每个过程作用见文章第四部分):

类加载器

生命周期的第一阶段,即加载阶段需要由类加载器来完成的,类加载器根据一个类的全限定名读取类的二进制字节流到JVM中,然后生成对应的java.lang.Class对象实例,

在虚拟机默认提供了3种类加载器,启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用类加载器(Application ClassLoader),如果有必要还可以加入自己定义的类加载器。

(1)启动类加载器(Bootstrap ClassLoader):负责加载 在<JAVA_HOME>\lib目录 和 被-Xbootclasspath参数所指定的路径中的类库 

(2)扩展类加载器(Extension ClassLoader):负责加载 <JAVA_HOME>\lib\ext目录 和 被java.ext.dirs系统变量所指定的路径中的所有类库

(3)应用程序类加载器(Application ClassLoader):负责加载用户类路径classPath所指定的类库,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

(4)自定义加载器(CustomClassLoader):属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现。

任意一个类在JVM中的唯一性,是由加载它的类加载器和类的全限定名一共同确定的。因此,比较两个类是否“相等”的前提是这两个类是由同一个类加载器加载的,否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。JVM的类加载机制,规定一个类有且只有一个类加载器对它进行加载。而如何保证这个只有一个类加载器对它进行加载呢?则是由双亲委派模型来实现的。

双亲委派模型

双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父类加载器。(类加载器之间的父子关系不是以继承的关系实现,而是使用组合关系来复用父加载器的代码)

为什么叫双亲委派

既然称为双亲委派机制,则双亲是必须存在的。ClassLoader类存在一个parent属性。因此可以配置双亲。这个双亲是指ExtClassLoader和AppClassLoader,在JDK中则是这样设置:

双亲委派模型的工作原理

如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层级的类加载器都是如此,因此所有请求最终都会被传到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

因此,加载过程可以看成自底向上检查类是否已经加载,然后自顶向下加载类。

双亲委派模型的优点

  • 使用双亲委派模型来组织类加载器之间的关系,Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
  • 避免类的重复加载,当父类加载器已经加载了该类时,子类加载器就没必要再加载一次。
  • 解决各个类加载器的基础类的统一问题,越基础的类由越上层的加载器进行加载。避免Java核心API中的类被随意替换,规避风险,防止核心API库被随意篡改

例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。

自定义类加载器

  • 继承ClassLoader   
  • 重写findClass()方法  
  • 调用defineClass()方法
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;

/**
 * 自定义类加载器
 */
public class MyClassLoader extends ClassLoader{

    /**
     * 此方法根据双亲委派机制向上传递,向下委派(不一定是自定义的类加载器加载)
     * loadClass则是直接指定自定义类加载器加载
     * @param name 全类名
     * @return
     * @throws ClassNotFoundException
     */
    @Override
//    public Class<?> loadClass(String name) throws ClassNotFoundException {
    public Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 通过文件流读取.class文件
            String classFile = name.substring(name.lastIndexOf(".") + 1) + ".class";
            // 获取二进制的文件流
            InputStream in = getClass().getResourceAsStream(classFile);
            if(in == null){
                return super.loadClass(name);
            }
            byte[] bytes = new byte[in.available()];
            in.read(bytes);
            Class<?> aClass = defineClass(name, bytes, 0, bytes.length);
            return aClass;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 测试类自定义加载器
     * @param args
     */
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> c = classLoader.loadClass("appoint.TestLoadClass");
        if(c!=null){
            Object obj=c.newInstance();
            Method method=c.getMethod("name", null);
            method.invoke(obj, null);
            System.out.println(c.getClassLoader().toString());
        }
    }
}

值得注意的是findClass和loadClass两个方法,findClass符合双亲委派机制,向上传递,向下委派,loadClass重写则会覆盖双亲委派逻辑,直接加载类则使用自定义类加载器加载

以下是Java默认的loadClass方法:

// Java双亲委派逻辑
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) {
                        //父加载器不为空,调用父加载器的loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空则,调用Bootstrap Classloader
                        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();
                    //父加载器没有找到,则调用findclass
                    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()
                resolveClass(c);
            }
            return c;
        }
    }

总结

Java双亲委派机制有效防止了重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。同时也保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

希望本文对大家有所帮助~~

 

 

参考文章

https://blog.csdn.net/a745233700/article/details/90232862

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值