关于java类加载器

本文详细介绍了JVM中类的加载过程,包括类加载器的工作原理及其层次关系,重点阐述了双亲委派模型如何确保类加载的一致性和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.类加载过程

JVM加载Class文件到内存的方式:

  • 一是隐式加载:继承或引用某个类时,有JVM负责加载;
  • 二是显式加载:在代码中调用loadClass(),Class.forName,ClassLoader的findClass方法等,显式加载中也可能包含隐式加载;

- ClassLoader的重要方法:

findClass:主要由URLClassLoader实现,根据URLClassPath去指定地方查找class文件;取得要加载class文件的字节流;

defineCLass:可以将字节流解析成Class对象,该Class对象并未进行resolve;
resolveClass:对Class对象进行Link,载入引用类(超类,接口字段,方法签名,方法中的本地变量);

loadClass:采用默认的加载逻辑根据类名加载一个类,返回Class对象,调用前面3个方法实现;

加载class文件的过程:

加载字节码到内存:findClass和defineClass方法
首先来看看URLClassLoader中的findClass方法,因为加载的第一步,找到并获取指定class文件的字节流;

protected Class<?> findClass(final String name)
            throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            //获取特权,确保有权限可以读取到资源,这里
            result = AccessController.doPrivileged(
                    new PrivilegedExceptionAction<Class<?>>() {
                        public Class<?> run() throws ClassNotFoundException {
                            //将完整的类名转换成文件路径格式
                            String path = name.replace('.', '/').concat(".class");
                            //在指定的URLPath中获取对应的class文件资源
                            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) {
            //因为要在特权操作中抛出ClassNotFoundException,使用了PrivilegedExceptionAction回调
            //它会将异常包装,这里要解除包装
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

关于doPrivileged的使用请看链接https://blog.youkuaiyun.com/wxmnq66/article/details/97140938

findClass方法的作用就是找到并借助defineClass方法返回Class对象
接下来看看findClass中使用的defineClass方法:

private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
        //首先加载包
        if (i != -1) {
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            Manifest man = res.getManifest();
            definePackageInternal(pkgname, man, url);
        }
        // Now read the class bytes and define the class
        //使用nio,获取字节缓冲区
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, bb, cs);
        } else {
            //获取不到缓冲区,直接InputStream获取字节数组
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, b, 0, b.length, cs);
        }
    }

在这个方法中,我们可以看到对于class文件的读取策略,其中CodeSigner和CodeSource分别是代码签名和代码源

验证和解析:defineClass方法和resovleClass方法:

defineClass首先进行:
(1)字节码验证;
(2)类准备:准备类中每个字段、方法和实现接口所需的数据结构;

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
            throws ClassFormatError
    {
        //首先检查类名;阻止加载“java.”开头包内的类(应有BootStrapLoader加载);
        //确保同一个包内的Class拥有相同的证书
        protectionDomain = preDefineClass(name, protectionDomain);
        //根据CodeSource获取一个URL的字符串表示
        String source = defineClassSourceLocation(protectionDomain);
        //字节码验证;类准备(准备字段,方法,实现接口所必需的数据结构);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        //通过证书为该类设置签名
        postDefineClass(c, protectionDomain);
        return c;
    }

2.类加载器简述

JVM自带加载器*

  • 启动类加载器 BootStrap ClassLoader:最顶层的类加载器,负责加载 JAVA_HOME\lib目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。可以通System.getProperty(“sun.boot.class.path”)查看加载的路径。
  • 扩展类加载器 ExtentionClassLoader:主要加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件,或通过java.ext.dirs系统变量指定路径中的类库。也可以通过System.out.println(System.getProperty(“java.ext.dirs”))查看加载类文件的路径。
  • 应用程序类加载器 ApplicationClassLoader:也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路径classpath上的类库。如果没有自定义类加载器,一般这个就是默认的类加载器。

类加载层次关系

类加载器之间的这种层次关系叫做双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。

类加载机制-双亲委托

当JVM加载Test.class类的时候

  • 首先会到自定义加载器中查找,看是否已经加载过,如果已经加载过,则返回该类。
  • 如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class。
  • 如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过。
  • 如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过。
  • 如果BoopStrapClassLoader没有加载过,则到自己指定类加载路径sun.boot.class.path下查看是否有Test.class字节码,有则加载并返回加载后的类c= findBootstrapClassOrNull(name)。
  • 如果还是没找到调用c =findClass(name)到加载器ExtClassLoader指定的类加载路径java.ext.dirs下查找class文件,有则加载并返回类。
  • 依此类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException。

这里注意
每个自定义的类加载器都需要重写findClass方法,该方法的作用是到指定位置查找class文件并加载到JVM中,如果找不到则抛出ClassNotFoundException异常。

双亲委派模型最大的好处就是让Java类同其类加载器一起具备了一种带优先级的层次关系。这句话可能不好理解,我们举个例子。比如我们要加载顶层的Java类——java.lang.Object类,无论我们用哪个类加载器去加载Object类,这个加载请求最终都会委托给Bootstrap ClassLoader,这样就保证了所有加载器加载的Object类都是同一个类。

双亲委派模型的实现比较简单,在java.lang.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 (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;
        }
    }


protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

原文链接:https://juejin.im/post/5bbc18f05188255c4834ce99
原文链接:https://blog.youkuaiyun.com/zerohuan/article/details/48738649

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值