Java 类加载机制(从源码来看双亲委派机制)

Java 类加载机制

1、类加载过程

class ----> loading (加载)----> linking(校验、准备、解析) ----> initializing(初始化)----> GC

Linking 中被分为了 verification、preparation、resolution 三个部分

loading:将字节码文件加载到内存中去

link:

​ verification:校验,验证class 字节码文件是否符合class文件的标准

​ preparation:准备,将class 中的静态变量赋默认值

​ resolution:消除,将class 中用到的符号引用转换为内存引用,可以理解为内存地址

initializing:这个时候将静态变量赋初始值,调用静态代码块

2、类加载器

类加载器就是执行 load 这一步,将字节码加载到内存中去

当将字节码加载到内存中去之后,也会创建一个Class类对象,指向字节码在内存中的位置,

通过这个对象对字节码进行解析

Boostrap <------ Extension <------ App <------ Custom ClassLoader

Boostrap:加载lib/rt.jar charset.jar 等核心类,C++ 实现

Extension:加载扩展jar包 jre/lib.ext/*.jar 或由 -Djava.ext.dirs 指定

App:加载classpath指定内容

CustomClassLoader:自定义ClassLoader

双亲委派机制:先自底向上检查该类是否已经加载,如果都没有加载,就自顶向下进行实际查找和加载child方向

为什么?

​ 1、保证安全问题

​ 2、保证不重复加载

源码分析:此次我们主要对ClassLoader 中的 loadClass方法和构造方法进行分析

2.1 构造方法源码分析

首先我们来看构造方法,构造方法能够解决我们很多疑问,例如ClassLoader 中的 parent 属性是什么时候赋值的。

protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

当我们调用无参构造时,会调用ClassLoader 中的另外一个构造方法,我们来看传入的这两个参数的方法

checkCreateClassLoader()

    private static Void checkCreateClassLoader() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        return null;
    }

这个方法主要是鉴定权限, java.lang.SecurityManager.checkCreateClassLoader() 方法抛出一个SecurityException如果调用线程不允许创建新的类加载器。此方法调用checkPermission与RuntimePermission(“createClassLoader”)权限。如果重写此方法,那么调用super.checkCreateClassLoader重载方法通常会抛出一个异常。

然后就是 getSystemClassLoader()方法

    public static ClassLoader getSystemClassLoader() {
      	// 初始化系统类加载器
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
      	// 鉴权
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

ClassLoader内部维护了两个与加载系统类加载器相关的静态变量,从注释中我们可以看出scl就是用来接收系统类加载器的,而sclSet则是一个布尔值,用来判断scl是否为空的,关于系统类加载器基本都是操作这两个值的。

    // The class loader for the system
    // @GuardedBy("ClassLoader.class")
    private static ClassLoader scl;

    // Set to true once the system class loader has been set
    // @GuardedBy("ClassLoader.class")
    private static boolean sclSet;

调用initSystemClassLoader(),并且做了一些安全检查,直接返回系统类加载器scl

    private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
          	// 得到 Launcher
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
              	// 得到系统类加载器
                scl = l.getClassLoader();
                try {
                  	// 是否有自定义类加载器
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }

initSystemClassLoader主要就是初始化系统类加载器的,但是若用户自定义了类加载器,就会将scl初始化为用户自定义类加载器,这部分是通过ClassLoader的一个内部类来实现的。

SystemClassLoaderAction是一个内部类,最后就是通过这个内部类来完成最后的类加载器初始化工作的。

class SystemClassLoaderAction
    implements PrivilegedExceptionAction<ClassLoader> {
    private ClassLoader parent;

    SystemClassLoaderAction(ClassLoader parent) {
        this.parent = parent;
    }

    public ClassLoader run() throws Exception {
        //从系统属性中获取key,即用户自定义类加载器的二进制名
        String cls = System.getProperty("java.system.class.loader");
        //若cls为空,则返回parent,即传入的系统类加载器
        if (cls == null) {
            return parent;
        }
		//通过这个自定义类加载器的二进制名,使用系统类加载器去将其加载,并将其初始化
        Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
        //通过反射创建这个自定义类加载器的实例
        ClassLoader sys = (ClassLoader) ctor.newInstance(
            new Object[] { parent });
        //将线程上下文类加载器设置为这个自定义类加载器
        Thread.currentThread().setContextClassLoader(sys);
        return sys;
    }
}

2.2 loadClass 方法源码分析(双亲委派机制的实现)

然后我们来看 loadClass方法

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}

当我们传入一个 name 时,其底层是调用了另一个 lodaClass 重载的方法,这里的第二个参数为 false 是表示 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 {
                  	// 如果 parent 不为空,就先执行 父加载器中的loadClass 方法,就又回到了第一步
                  	// 这里就能看到双亲委派机制了
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // parent 为空,表示为 bootstrap 类加载器,调用这个方法 
                        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;
        }
    }

此方法的重要代码都有注释,通俗易懂。

2.3 关于 Launcher

关于Launcher

3、自定义类加载器

自定义类加载器

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;

public class MyClassLoader extends ClassLoader{

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

        File file = new File("/Users/lcx/Desktop/ClassLoader/", name.replace(".", "/").concat(".class"));

        try {

            DataInputStream is = new DataInputStream(new FileInputStream(file));
            int len = (int)file.length();
            byte[] buff = new byte[len];
            is.readFully(buff);
            is.close();
            return defineClass(name, buff, 0, buff.length);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        System.out.println(classLoader.getParent());
        Class<?> helloWorld = classLoader.loadClass("HelloWorld");
        helloWorld.getMethod("doSome",String.class).invoke(helloWorld.newInstance(),"lcx");
    }
}

在传入的 /Users/lcx/Desktop/ClassLoader/文件夹中有 HelloWorld.class , 这个字节码是使用我们自定义的类加载器来实现加载的。

public class HelloWorld{
  
        public void doSome(String name){
                System.out.println("hello " + name);
        }
}

最终我们利用反射,执行这个类中的方法,实现了自定义类加载器。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_CX_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值