类的加载机制和加载器ClassLoader

本文深入探讨了Java类加载的过程,包括加载、验证、准备、解析、初始化和使用阶段。详细阐述了双亲委派模型,如何通过类加载器加载类,以及为何设计这样的机制。同时,介绍了自定义类加载器的实现,如何打破双亲委派,以及在实际应用中的场景。
摘要由CSDN通过智能技术生成

通过java命令执行java代码的大致流程如下:

在这里插入图片描述
ClassLoader类加载器调用loadClass方法把类加载到JVM中的过程为:
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 ->卸载

  • 加载:在硬盘上查找并通过IO读取字节码文件,用到时才会加载此类,比如执行main方法、new对象等等,在加载阶段会在内存中生成一个代表此类的java.lang.class类型的对象,作为方法区这个类的各种数据的访问入口。

  • 验证:校验字节码文件的正确性

  • 准备:给类的静态变量分配内存,并赋予默认值

  • 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用比如main())替换为指向数据所存内存的地址或者句柄等(直接引用),这是所谓的静态链接过程(在类加载期间完成),动态链接是指在程序运行期间把符号引用替换为直接引用;

  • 初始化:把类的静态变量初始化为指定值,并执行静态代码块。

类被加载到JVM方法区主要包含 运行时常量池、类元信息、字段信息、方法信息、类加载器的引用、对应Class实例的引用等信息
Class实例:类加载器在加载完类的信息到方法去后,会生成对应的class类型对象放到堆中,作为开发人员访问方法区中该类信息的入口和切入点。

类的懒加载,程序在运行时用到的相关类才会加载。jar或者war包中的类不是一次性全部加载到jvm中,是用到时才加载。
比如B b=null,这个B类就不会被加载。

类加载器和双亲委派机制

类的加载主要是通过类加载器ClassLoader来完成的,java中主要有四种加载器:

  • 引导类加载器BootStrapClassLoader:负责加载支撑JVM运行的位于jre的lib目录下的核心类库,比如rt.jar、charsets.jar等;
  • 扩展类加载器ExtClassLoader:负责加载 支撑JVM运行的位于jre的lib目录下ext扩展目录下的jar包;
  • 应用程序类加载器AppClassLoader:负责加载classpath下的类包,主要是自己的应用程序类;
  • 自定义类加载器:负责加载自定义路径下的类;

类加载器初始化过程
由上加载流程图可知,C++会调用java代码创建java执行器实列sun.misc.Launcher,它使用了单例模式,保证了整个JVM中只有一个sun.misc.Launcher实列;
在sun.misc.Launcher无参构造方法内部创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader扩展类加载器和
sun.misc.Launcher.AppClassLoader应用程序类加载器。
JVM使用launcher.getClassLoader()方法默认返回的是AppClassLoder实列加载我们的应用程序。

  public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
			//创建ExtClassLoader,设置其p父类加载器为null
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
			//创建AppClassLoader实例,并设置父类加载器为ExtClassLoader,并设置Launcher的ClassLoader属性为AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

}

双亲委派机制

在这里插入图片描述
流程描述:
传入类全限定名进行加载时,先到AppClassLoader中查找是否已经加载过,如果已经加载过,则直接返回,如果没有则委托ExtClassLoader父加载器进行加载,它也会先查找是否已经加载过此类,如果有则直接返回,如果没有则接着委托给BootStrapClassLoader进行加载,它先查询有没有加载过,如果有则直接返回,如果没有则查询自己负责的路径下有没有该类,如果有进行加载返回,如果没有则由子类加载器进行查找负责的路径下有没有该类进行查找,以此类推,如果都查找不到,则会报错。

看看双亲委派机制的源码;以上扩展类加载器和应用程序加载器都是继承了ClassLoader抽象类,加载类时,是调用其loaderClass方法。

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 {
						//如果没有父类加载器,则说明是扩展类加载器ExtClassLoader,则会调用引导类加载器进行加载
                        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;
        }
}


为什么要设计双亲委派机制:

  • 沙箱安全机制:自己写的java.lang.String类不会被加载,防止核心API库被篡改。
  • 避免类的重复加载:当父加载器已经加载该类时,没必要子加载器再加载一遍。

全盘负责委托机制:

全盘负责 是指当一个类被ClassLoader加载时,除非显式的调用另一个ClassLoader,那么该类所依赖及引用的类也是由此ClassLoader进行加载。

自定义类加载器:

自定义来加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法,一个是java.lang.ClassLoader#loadClass(java.lang.String, boolean)实现了双亲委派机制,另一个是java.lang.ClassLoader#findClass,这个通过类的全限定名读取当前负责路径下的class文件并返回class类型对象的方法;

public class MyClassLoaser extends ClassLoader {

    private String classPath;

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

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {

        try {
            byte[] bytes = loadByte(name);
            //defineClass方法会把class文件读取字节数组转成class对象
            Class<?> defineClass = defineClass(name, bytes, 0, bytes.length);
            return defineClass;
        } catch (Exception e) {
            throw new ClassNotFoundException();
        }


    }

    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
        String classUrl = classPath + "/" + name+".class";
        FileInputStream stream = new FileInputStream(classUrl);
        int len = stream.available();
        byte[] bytes = new byte[len];
        stream.read(bytes);
        stream.close();
        return bytes;
        //D:\classloader\com\bb
    }

    public static void main(String[] args) throws Exception {
        MyClassLoaser myClassLoaser = new MyClassLoaser("D:/classloader");
        Class<?> loadClass = myClassLoaser.loadClass("com.bijian.classloader.Math1");
        Object instance = loadClass.newInstance();
        Method method = loadClass.getDeclaredMethod("add", int.class, int.class);
        Object invoke = method.invoke(instance, 1, 2);
        System.out.println(invoke);
    }

打破双亲委派机制:

用自定义的类加载器打破双亲委派机制,只让目标类在自定义加载器进行加载,因为双亲委派机制的逻辑代码在Class<?> loadClass(String name, boolean resolve)方法中,所以要重写这个方法,把双亲委派机制的那块代码去掉就行,其它代码保留,但是有个问题就是任何一个对象都继承Object类,所以需要加上判断,如果是我们自定义路径下的包,才使用自定义加载器,其他对象还是使用ClassLoader对象中的loadClass方法:

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(!name.startsWith("com.bijian")){
               return super.loadClass(name,resolve);
           }

            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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值