类加载器

本文详细介绍了Java中的类加载器工作原理,特别是双亲委派模型。该模型确保基础类由顶层类加载器加载,防止类的多次加载和冲突。当自定义类加载器加载类时,会先尝试父加载器,然后自身加载。通过示例展示了自定义类加载器如何加载类的过程,强调了`findClass`和`defineClass`方法的作用。

类加载器

双亲委派模型

在这里插入图片描述

双亲委派模型工作过程

  1. 先问下这个类在当前加载器中有没有被加载,有就返回(直接引用),没有就往上抛。
  2. 如果到根加载器都没有加载过该类,那么根加载器尝试加载,如果成功就返回,失败就让低一级的加载器加载,直到成功为止。

如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其子类加载器去进行类加载。

双亲委派模型优点

java类随着它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。

例如:类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。

ClassLoader类
  • loadClass默认实现如下:
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
  • loadClass(String name, boolean resolve) 函数
    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 (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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

过程:
1,首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。

2,如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。

3,如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

  • loadClass默认实现如下:
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

可以看出,抽象类ClassLoaderfindClass函数默认是抛出异常的。因此必须重写父类findClass方法

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

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

函数调用过程
在这里插入图片描述

自定义加载器
package com.jvm.bytecode.java01;

import java.io.FileInputStream;

/**
 * 自定义加载器
 */
public class MyClassLoader extends ClassLoader {
    //文件路径
    private String classPath;

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

    //将.class 文件转化为字节数组
    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;
    }

    //重写findClass方法
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass invoked:" + name);
        try {
            byte[] data = loadByte(name);
            return defineClass(name,data,0,data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    public static void main(String[] args) throws Exception {
        //  加载项目包名
        MyClassLoader classLoader = new MyClassLoader("C:\\Users\\auas\\Desktop\\JVMDemo");
        //加载项目的类   ;注意要删除项目目录中的MyTest.class文件, 否则还是系统加载器加载器
        Class<?> clazz = classLoader.loadClass("com.jvm.bytecode.java01.MyTest");
        //实例化对象,并调用方法
        Object obj = clazz.newInstance();
    }

}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岿然如故

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值