侧边栏壁纸
博主头像
再见理想博主等级

只争朝夕,不负韶华

  • 累计撰写 112 篇文章
  • 累计创建 64 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

JVM-类加载的双亲委派机制

再见理想
2022-05-25 / 0 评论 / 0 点赞 / 333 阅读 / 1,507 字

一,类加载器

JVM-虚拟机类加载机制

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

好处:避免了类的重复加载,同时保护程序安全,防止核心API被随意篡改。


二,双亲委派模型

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java. lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。如果读者有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但永远无法被加载运行。

从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。


2.1 APP类加载器 Bootstrap ClassLoader

这是 JVM 的 根 ClassLoader,它是用C++实现的,JVM 启动时初始化此 ClassLoader,主要加载 java 的核心类库,rt,jar,resource.jar; 可以通过 XbootclassPath 指定核心类库的路径。


2.2 扩展类加载器 Extension ClassLoader

JVM 用此 classloader 来加载扩展功能的一些 jar 包,如 java.ext.dirs 系统属性所指的的目录, 或从 JDK 的安装目录的 jre/lib/ext 子目录下加载类库。如果用户创建的 jar 放在此目录下,也会自动由扩展类加载器加载。

扩展类加载器由 Java语言实现,代码中 getParent() 获取父加载器为 null。


2.3应用类加载器 AppClassLoader/System ClassLoader

JVM 用此 classloader 来加载启动参数中指定的 Classpath(位置:target包下) 中的 jar 包以及目录,在 Sun JDK 中 ClassLoader 对应的类名为 AppClassLoader。

三,java 8 中 ClassLoad.loadclass() 源码

双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调用父加载器的 loadClass() 方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出 ClassNotFoundException 异常后,再调用自己的findClass()方法进行加载。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 第一步,从缓存中检查当前类是否已加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 是一个递归调用,直至没有父类的类加载器(扩展类加载器的 parent = null)
                        c = parent.loadClass(name, false);
                    } else {
                       // 加载java核心类库的加载器
                        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;
        }
    }

四,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文件
                        String path = name.replace('.', '/').concat(".class");
                      //从classpath获取到.class文件
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                              //从路径中读取class文件字节流,并定义成类;todo bytebuffer
                                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;
    }

五,如何打破双亲委派机制

打破双亲委派机制
简书:聊聊JDBC是如何破坏双亲委派模型的
Tomcat是如何打破双亲委派机制的

0

评论区