java class 类加载过程,为什么要双亲委派机制(上)

类加载是怎么样的?从哪里开始加载,如何加载

前提知识
在这里插入图片描述

  1. 虚拟机自带的加载器:

启动类加载器(Bootstrap)C++
扩展类加载器(Extension)Java
应用程序类加载器(AppClassLoader)
Java也叫系统类加载器,加载当前应用的classpath的所有类

  1. AppClassLoader 和 Extension 都是Launcher的静态类
    在这里插入图片描述

(1)扩展类加载器 运行 ${JAVA_HOME}/jre/lib/ext 下面的 jar 包

ExtensionClassLoader,这个类加载器其实也是类似的,就是你的Java安装目录下,有一个“lib\ext”目录。这里面有一些类,就是需要使用这个类加载器来加载的,支撑你的系统的运行。是从Launcher 类里面获取到的

关键属性:类文件地址是从java.ext.dirs 获取的

 String s = System.getProperty("java.ext.dirs");
 //打印得出来的地址是
 /Users/hilbert/Library/Java/Extensions:
 /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:
 /Library/Java/Extensions:/Network/Library/Java/Extensions:
 /System/Library/Java/Extensions:
 /usr/lib/java

打开 ${JAVA_HOME}/jre/lib/ext 地址发现:里面存的是jar包嘛,我们的项目maven引用其实也是jar 包的形式嘛
在这里插入图片描述
解压一个jar 包的效果:存的也只有包路径,不会有文件路径的。(com.wei.xxxx)


在这里插入图片描述

ExtClassLoader 具体类的方法

static class ExtClassLoader extends URLClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }
    private static volatile ExtClassLoader instance = null;

    //双重判断加载实例嘛
    public static ExtClassLoader getExtClassLoader() throws IOException
    {
        if (instance == null) {
            synchronized(ExtClassLoader.class) {
                if (instance == null) {
                    instance = createExtClassLoader();
                }
            }
        }
        return instance;
    }

    private static ExtClassLoader createExtClassLoader() throws IOException {
        try {
            return AccessController.doPrivileged(
                new PrivilegedExceptionAction<ExtClassLoader>() {
                    public ExtClassLoader run() throws IOException {
                        //获取类文件地址
                        final File[] dirs = getExtDirs();
                        int len = dirs.length;
                        for (int i = 0; i < len; i++) {
                            MetaIndex.registerDirectory(dirs[i]);
                        }
                        return new ExtClassLoader(dirs);
                    }
                });
        } catch (java.security.PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
    }
    
     public ExtClassLoader(File[] dirs) throws IOException {
         //设置parent classLoader
        super(getExtURLs(dirs), null, factory);
        SharedSecrets.getJavaNetAccess().
            getURLClassPath(this).initLookupCache(this);
    }
    
    //获取java.jar文件的地址
     private static File[] getExtDirs() {
        String s = System.getProperty("java.ext.dirs");
        File[] dirs;
        if (s != null) {
            StringTokenizer st =
                new StringTokenizer(s, File.pathSeparator);
            int count = st.countTokens();
            dirs = new File[count];
            for (int i = 0; i < count; i++) {
                dirs[i] = new File(st.nextToken());
            }
        } else {
            dirs = new File[0];
        }
        return dirs;
    }

    private static URL[] getExtURLs(File[] dirs) throws IOException {
        Vector<URL> urls = new Vector<URL>();
        for (int i = 0; i < dirs.length; i++) {
            String[] files = dirs[i].list();
            if (files != null) {
                for (int j = 0; j < files.length; j++) {
                    if (!files[j].equals("meta-index")) {
                        File f = new File(dirs[i], files[j]);
                        urls.add(getFileURL(f));
                    }
                }
            }
        }
        URL[] ua = new URL[urls.size()];
        urls.copyInto(ua);
        return ua;
    }
}

(2)应用程序类加载器 (加载用户自定义 -classpath 或者 Jar 包的 Class-Path 定义的第三方包)

ApplicationClassLoader,这类加载器就负责去加载“ClassPath”环境变量所指定的路径中的类。其实你大致就理解为去加载你写好的Java代码吧,这个类加载器就负责加载你写好的那些类到内存里。这些地址是URLClassLoader 的URLClassPath ucp 属性

String s = System.getProperty("java.class.path"); 获取都是jar 包的地址
//打印出来的地址 第三方的包
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/deploy.jar:
/Users/hilbert/.m2/repository/org/mybatis/mybatis/3.5.1/mybatis-3.5.1.jar:
/Users/hilbert/.m2/repository/org/openjdk/jol/jol-core/0.9/jol-core-0.9.jar

AppClassLoader 具体类的方法

static class AppClassLoader extends URLClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }

    public static ClassLoader getAppClassLoader(final ClassLoader extcl)
        throws IOException
    {
        //jar 包所在地址
        final String s = System.getProperty("java.class.path");
        final File[] path = (s == null) ? new File[0] : getClassPath(s);

        return AccessController.doPrivileged(
            new PrivilegedAction<AppClassLoader>() {
                public AppClassLoader run() {
                URL[] urls =
                    (s == null) ? new URL[0] : pathToURLs(path);
                    //extcl 是ExtClassLoader
                return new AppClassLoader(urls, extcl);
            }
        });
    }

    final URLClassPath ucp;

    AppClassLoader(URL[] urls, ClassLoader parent) {
        //设置ExtClassLoader 为parent ClassLoader
        super(urls, parent, factory);
        ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
        ucp.initLookupCache(this);
    }
	
	//AppClassLoader 加载类的方法
    public Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        int i = name.lastIndexOf('.');
        if (i != -1) {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPackageAccess(name.substring(0, i));
            }
        }
        //查看这个包地址在不在锁管理的jar范围内
        if (ucp.knownToNotExist(name)) {
            Class<?> c = findLoadedClass(name);
            if (c != null) {
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
            throw new ClassNotFoundException(name);
        }

        return (super.loadClass(name, resolve));
    }
}

(3)启动类加载器 负责加载 ${JAVA_HOME}/jre/lib 部分 jar 包

BootstrapClassLoader,他主要是负责加载我们在机器上安装的Java目录下的核心类的。所以一旦你的JVM启动,那么首先就会依托启动类加载器,去加载你的Java安装目录下的“lib”目录中的核心类库。

Launcher 里面有一个核心方法
private static String bootClassPath =System.getProperty("sun.boot.class.path");
//查出来的是:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes

对外提供了查询BootStrap管理jar 的地址方法 也是跟URLClassLoader的udp 属性差不多

public static URLClassPath getBootstrapClassPath() {
    return BootClassPathHolder.bcp;
}

private static class BootClassPathHolder {
	//bcp 指向的地址是 System.getProperty("sun.boot.class.path")
    static final URLClassPath bcp;
    static {
        URL[] urls;
        if (bootClassPath != null) {
            urls = AccessController.doPrivileged(
                new PrivilegedAction<URL[]>() {
                    public URL[] run() {
                        File[] classPath = getClassPath(bootClassPath);
                        int len = classPath.length;
                        Set<File> seenDirs = new HashSet<File>();
                        for (int i = 0; i < len; i++) {
                            File curEntry = classPath[i];
                            
                            if (!curEntry.isDirectory()) {
                                curEntry = curEntry.getParentFile();
                            }
                            if (curEntry != null && seenDirs.add(curEntry)) {
                                MetaIndex.registerDirectory(curEntry);
                            }
                        }
                        return pathToURLs(classPath);
                    }
                }
            );
        } else {
            urls = new URL[0];
        }
        bcp = new URLClassPath(urls, factory, null);
        bcp.initLookupCache(null);
    }
}

简单知道我们平常的类加载器是什么

在这里插入图片描述

public class ClassLoaderA {
    private Integer a = new Integer(100);

    public void add() {
        a++;
    }

    public Integer getA() {
        return a;
    }

    public void setA(Integer a) {
        this.a = a;
    }
}

public void test() {
    ClassLoaderA classLoaderA = new ClassLoaderA();
    System.out.println(classLoaderA.getClass().getClassLoader().getParent().getParent());
    System.out.println(classLoaderA.getClass().getClassLoader().getParent());
    System.out.println(classLoaderA.getClass().getClassLoader());
}
//运行结果。说明ExtClassLoader 没有父类,那代码怎么运行到的?
null
sun.misc.Launcher$ExtClassLoader@4fca772d
sun.misc.Launcher$AppClassLoader@18b4aac2

类加载过程:其实将一个class 文件load 到内存中

 public static void main(String[] args) throws Exception {
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    //类的包路径
    Class<?> a = classLoader.loadClass("锁.ClassLoader.ClassLoaderA");
    ClassLoaderA instance = (ClassLoaderA) a.newInstance();
    System.out.println(instance.getA());
}
//执行结果
100    

(1)ClassLoader classLoader = ClassLoader.getSystemClassLoader();

//SystemClassLoader其实就是Launcher 的AppClassLoader
public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        //从launcher获取AppClassLoader
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
            Throwable oops = null;
            scl = l.getClassLoader();
            try {
                //java 沙箱机制(下一篇文章会讲到)
                scl = AccessController.doPrivileged(
                    new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
                    oops = oops.getCause();
                }
            }
            。。。。
        }
        sclSet = true;
    }
}

(2) Class<?> a = classLoader.loadClass(“锁.ClassLoader.ClassLoaderA”)

  1. 先调用的是appClassLoader的loadClass 方法
  2. 判断这个包路径在不在自己管理的范围内
  3. 判断是否已经加载过了。就不需要加载了
  4. 调用父类的加载方法(并不是parentClassLoader:ExtClassLoader的加载方法)
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    int i = name.lastIndexOf('.');
    if (i != -1) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPackageAccess(name.substring(0, i));
        }
    }
    
    //1.判断这个包路径在不在自己管理的范围内
    if (ucp.knownToNotExist(name)) {
        //2.判断是否已经加载过了。就不需要加载了
        Class<?> c = findLoadedClass(name);
        if (c != null) {
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
        throw new ClassNotFoundException(name);
    }
    //3.调用父类的加载方法(并不是parentClassLoader:ExtClassLoader的加载方法)
    return (super.loadClass(name, resolve));
}

(3) super.loadClass(name, resolve)

  • 加一个正在加载对象的锁
  • 判断一下是否加载过了
  • 如果父加载器不为空的时候,使用父加载器去加载
  • 如果父类为空的时候,使用bootStrap加载器
  • 如果上一层取出来为空的话就用了自己的加载类的方法 ,使用URLClassLoader方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    //1.加一个正在加载对象的锁
    synchronized (getClassLoadingLock(name)) {
        // 2.判断一下是否加载过了。
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //3如果父加载器不为空的时候,使用父加载器去加载
                    c = parent.loadClass(name, false);
                } else {
                    //4如果父类为空的时候,使用bootStrap加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                
            }
            //如果上一层取出来为空的话就用了自己的加载类的方法
            if (c == null) {
                long t1 = System.nanoTime();
                //使用自己的类加载方法,使用URLClassLoader 方法
                c = findClass(name);
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

//正在生成这个对象的锁,防止重复加载
protected Object getClassLoadingLock(String className) {
    Object lock = this;
    if (parallelLockMap != null) {
        Object newLock = new Object();
        lock = parallelLockMap.putIfAbsent(className, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

URLClassLoader findClass 方法

protected Class<?> findClass(final String name) throws ClassNotFoundException{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    //查找对应的class文件对象  锁/ClassLoader/ClassLoaderA.class
                    String path = name.replace('.', '/').concat(".class");
                    //从ucp获取资源地址文件
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            //获取class文件 
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

类加载器流程

在这里插入图片描述

(1)双亲委派机制的好处,为什么要从BootStrapClassLoader 开始加载,而不是从AppClassLoader 开始加载有什么问题?为什么开头是AppClassLoader 但是要往上找,而不是先从自身开始找。

BootStrapClassLoader 和AppClassLoader 加载的jar 包不一样,BootStrap
主要是加载的是核心类,支持Java程序的启动,所以需要最先加载,但是不能放在最前面,因为后面加载不到了,加载的都是用户自定义的包,所以不能放在最前面。要让AppClassLoader放在最前面,高频使用。提高效率。(有缓存,可以防止重复加载)

(2)我用一个类加器就可以了?为什么要那么多类加载器。比如我AppClassLoader 的jar查询地址,放所有的文件的jar不可以吗?会有什么问题?(或者是分层次加载有什么好处?)
Jar 的加载顺序是怎么样的 ?关键代码如下:

//查找对应的class文件对象 锁/ClassLoader/ClassLoaderA.class
String path =name.replace(’.’, ‘/’).concat(".class");
//从ucp获取资源地址文件
Resource res =ucp.getResource(path, false);
//获取Class文件
return defineClass(name,res);

重点是ucp.getResource方法 已经决定了用哪个class 文件

  • 每个ClassLoader 下面会有一些jar 文件,每个jar 文件就是一个JarLoader
    在这里插入图片描述
public Resource getResource(String name, boolean check) {
    if (DEBUG) {
        System.err.println("URLClassPath.getResource(\"" + name + "\")");
    }

    Loader loader;
    //获取缓存里面符号这个class文件的jar包位置下标
    int[] cache = getLookupCache(name);
    for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) {
        //从jar里面解析获取对应的class文件。谁的下标越早就越容易找到
        Resource res = loader.getResource(name, check);
        if (res != null) {
            return res;
        }
    }
    return null;
}

private synchronized int[] getLookupCache(String name) {
        //lookupCacheURLs包含已经加载并缓存的URL路径
        //lookupCacheEnabled参数表示是否启用查找缓存,通过属性sun.cds.enableSharedLookupCache指定,默认为true
        if (lookupCacheURLs == null || !lookupCacheEnabled) {
            return null;
        }
        //lookupCacheLoader在initLookupCache方法中完成初始化,该方法在URLClassPath实例化完成调用,通常是URLClassLoader的父类加载器
        //getLookupCacheForClassLoader是一个本地方法,该方法返回包含指定name的资源的路径在URL[]中的索引
        //比如URL[]为{a.jar, b.jar, c.jar, d.jar},a.jar和c.jar都包含有foo/Bar.class,则该方法返回[0,2]
        int[] cache = getLookupCacheForClassLoader(lookupCacheLoader, name);
        if (cache != null && cache.length > 0) {
            int maxindex = cache[cache.length - 1]; // cache[] is strictly ascending.
            //确保对应路径的Loader对象已实例化
            if (!ensureLoaderOpened(maxindex)) {
                if (DEBUG_LOOKUP_CACHE) {
                    System.out.println("Expanded loaders FAILED " +
                                       loaders.size() + " for maxindex=" + maxindex);
                }
                return null;
            }
        }
        return cache;
}

那么class 类的加载顺序是跟jar 包的顺序有关系的(如果jar 里面还依赖其他jar包怎么办)

private synchronized Loader getNextLoader(int[] cache, int index) {
    if (closed) {
        return null;
    }
    if (cache != null) {
        if (index < cache.length) {
            Loader loader = loaders.get(cache[index]);
            if (DEBUG_LOOKUP_CACHE) {
                System.out.println("HASCACHE: Loading from : " + cache[index]
                                   + " = " + loader.getBaseURL());
            }
            return loader;
        } else {
            return null; // finished iterating over cache[]
        }
    } else {
        return getLoader(index);
    }
}

//重点getLoader 方法 有一个栈在生效
private synchronized Loader getLoader(int index) {
    if (closed) {
        return null;
    }
    while (loaders.size() < index + 1) {
        URL url;
        synchronized (urls) {//urls 是我们jar 地址
            if (urls.empty()) {
                return null;
            } else {
                url = urls.pop();
            }
        }
        
        String urlNoFragString = URLUtil.urlNoFragString(url);
        if (lmap.containsKey(urlNoFragString)) {
            continue;
        }
        Loader loader;
        try {
            loader = getLoader(url);//生成这个jar 对应JarLoader
            URL[] urls = loader.getClassPath();//找到这个jar依赖的其他jar
            if (urls != null) {
                //把这些依赖的jar 加入到这个栈
                push(urls);
            }
        } catch (IOException e) {
            continue;
        } catch (SecurityException se) {
            if (DEBUG) {
                System.err.println("Failed to access " + url + ", " + se );
            }
            continue;
        }
        validateLookupCache(loaders.size(), urlNoFragString);
        loaders.add(loader);
        lmap.put(urlNoFragString, loader);
    }
    if (DEBUG_LOOKUP_CACHE) {
        System.out.println("NOCACHE: Loading from : " + index );
    }
    return loaders.get(index);
}

利用一个栈栈和一个队列 完成了class文件的加载顺序

在这里插入图片描述

  1. 加载顺序:

在这里插入图片描述

  1. 变成 :
    在这里插入图片描述
  2. 最终:
    在这里插入图片描述

appClassLoader的顺序是:部分核心库->项目自身->项目第三方依赖,可以从System.getProperty(“java.class.path”) 查看

(1)这时候出现同包名java.lang.String(自定义的类跟系统的类出现冲突怎么办,怎么加载)?

//自己项目定义的
package java.lang;
public class String {
    public static void main(String[] args) {
        System.out.println("test");
    }
}

产生报错:因为他优先从bootStrap先找到了,所以不会加载
在这里插入图片描述

这是双亲委派的好处之一:
(1)防止一些第三方提供的jar包里面有恶意的class文件(例如java.lang.String)把你的程序破坏掉。

java class 类加载过程,为什么要双亲委派机制(下)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值