java-jvm-双亲委派机制、什么时候加载一个类.md

前言

  • JVM 是如何加载一个类?
  • 什么是双亲委派机制?
  • 什么时候上级类无法加载?

类加载的分类

  1. 启动类加载器 bootstrap classloader :加载jre/lib/rt.jar
  2. 扩展类加载器 extension classloader :加载jre/lib/ext/*.jar
  3. 应用程序类加载器 application classloader:加载classpath上指定的类库

JVM 如何加载一个类

  • Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候会准备应用程序运行中需要的类加载器。
  • Launcher作为JAVA应用的入口,根据双亲委派模型,Laucher是由JVM创建的,它类加载器应该是BootStrapClassLoader

Launcher-源码

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        //获取ExtCkassLoader
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        //获取AppClassLoader
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
}

ExtClassLoader 对象创建

  • 可以看到 ExtClassLoader 是Launcher的一个内部类,而且是单例模式
static class ExtClassLoader extends URLClassLoader {
    private static volatile Launcher.ExtClassLoader instance;

    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
        if (instance == null) {
            Class var0 = Launcher.ExtClassLoader.class;
            synchronized(Launcher.ExtClassLoader.class) {
                if (instance == null) {
                    //去创建对象
                    instance = createExtClassLoader();
                }
            }
        }
        return instance;
    }
    public ExtClassLoader(File[] var1) throws IOException {
        //可以看到去调用父类的时候 classLoader为null,那也就说在调用,
        super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
        SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    } 
}

ExtClassLoader对象创建过程

  • 通过上面源码分析,在创建对象的时候去调用了父类的构造函数,首先调用的是URLClassLoader

public URLClassLoader(URL[] urls, ClassLoader parent,
                      URLStreamHandlerFactory factory) {
    //将 加载器传递给了父类
    super(parent);
    //...code
}
protected SecureClassLoader(ClassLoader parent) {
    super(parent);
    //...code
}
protected ClassLoader(ClassLoader parent) {
    this(checkCreateClassLoader(), parent);
}
//最终将 parent的赋值放到了这里,
private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    //code...
}

什么是双亲委派机制

  • 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,以此类推,这样所有的加载请求最终都会传到最顶层的启动类加载器, 如果上级的类加载器无法加载,自己才会去加载这个类。
  • 需要说明的是,上级并不是我的父类,而是由类加载器创建对象的时候,传入的加载器,他们的构成是由类似于链表的结构,

代码体现

  1. 创建一个类,
  2. 添加一个main方法,作为程序入口
  3. 搜索URLClassLoader找到其子类AppClassLoader

AppClassLoader-loadClass源码

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
    int var3 = var1.lastIndexOf(46);
    if (var3 != -1) {
        SecurityManager var4 = System.getSecurityManager();
        if (var4 != null) {
            //判断调用线程有没有权限访问指定的类
            var4.checkPackageAccess(var1.substring(0, var3));
        }
    }
	//判断有没有加载,如果没有加载让上级去加载
    if (this.ucp.knownToNotExist(var1)) {
        //加载了,返回
        Class var5 = this.findLoadedClass(var1);
        if (var5 != null) {
            if (var2) {
                this.resolveClass(var5);
            }
            return var5;
        } else {
            //否则抛出异常
            throw new ClassNotFoundException(var1);
        }
    } else {
        //让父类去加载
        return super.loadClass(var1, var2);
    }
}

什么时候上级类加载器无法加载

  • 需要注意的是findClass这个方法,也就是说当bootstrapClassLoader无法加载的时候,就会去调用findClass这个方法,需要注意的是,URLClassLoader重写了findClass这个方法,那也就是说ExtClassLoader是加载jar包中的内容,而我们的自己写的类则是由URLClassLoader.findClass()所加载
    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 如果是Null 则交给 `bootstrapClassLoader` 去处理
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {

                    long t1 = System.nanoTime();
                    //这个由当前类去调用,比如如果当前的this是 App的话则会去调用父类的 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(c);
            }
            return c;
        }
    }

为什么要有双亲委派

防止内存出现多份同样的字节码,为什么这么说呢?

  1. BootstrapClassLoader 加载的是 java 的核心库 java.*下的 jar包
  2. ExtClassLoader 加载的是 java 的扩展库 javax.*下的jar包
  3. AppClassLoader 加载的是我们自己的 开发的 jar包

比如 java.* 下就有 String 这个 class 文件 时由 BootstrapClassLoader 加载到内存的

为什么这么做呢?

假定我们开发人员 自定义了一个 String 查找顺序就是

  1. AppClassLoader 委托给 ExtClassLoader 委托给 BootstrapClassLoader 最后 BootstrapClassLoader 发现内存中已经有了 String 这个 就不允许在 加载了…
  2. 简单来就是为了 防止内存出现多份相同的字节码…也保证了安全性,防止核心 Class 被修改

最终说明

通过对上面源码的分析,其实双亲委派机制是由

  1. JVM创建一个Launcher作为程序入口
  2. 在创建Launcher的时候创建了ExtClassLoader加载器,也就是Extension ClassLoader
  3. 然后然后创建了AppClassLoader加载器
  4. 那怎么体现双亲委派机制的呢?就是在调用 ClassLoaderloadClass方法的时候
    1. AppClassLoader去调用loadClass()的时候
    2. 判断 parent 这个属性是不是null,如果是null则交由bootstrapClassLoader去处理
    3. 如果不是null则由ExtClassLoader去处理,
    4. 创建ExtClassLoader的时候 parent是null 则交给 bootstrapClassLoader
    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 如果是Null 则交给 `bootstrapClassLoader` 去处理
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //这个由当前类去调用,比如如果当前的this是 App的话则会去调用父类的 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(c);
            }
            return c;
        }
    }

图解

最终分析后可以得出双亲委派模型图如下(要注意这里的类加载器的父子关系不是以继承的关系实现的,而是通过组合的方式,即内部变量parent指向父加载器)

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值