JAVA类加载机制详解

上一篇文章我们简单说了一下类的创建过程,但是如果JVM需要加载类,会经过哪些具体的过程呢?下面我们就来谈一谈。

要了解加载类的过程,我们就必须要了解类加载器。

在很多初学者刚听到类加载器的时候觉得很高大上,其实类加载器无非就是和我们平时说的Class对象而已。

在java里面我们经常用到的类加载器可能就是以下几种:

  • 引导(启动)类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等 bootstrtload------>jre/lib目录
  • 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JA类包 extClassLoad------->jre/lib/ext目录下
  • 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类 appClassLoad------>classPath目录下
  • 自定义加载器:负责加载用户自定义路径下的类包

类加载原理:主要使用双亲委派机制

双亲委派机制:一个类进来先由应用类加载器------->扩展类加载器------->启动类加载器 然后启动类建加载器尝试加载 因为启动类加载器去加载jre包下面的lib目录 而我们写的类在classpath下面 启动类加载器加载不到就会像扩展类加载器(默认加载ext包下面的)去下发------------>应用类加载器 (从claaPath下面查找)

总结就是:双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载

双亲委派详情具体执行过程: 调用findClassLoad 判断有没有被加载 如果没有被加载 判断parent是否为空 如果为空调用bootstrap引导类加载器,extClassLoad里面是没有findClass()方法的 然后去调用URLCalssLoad里面的findClass()方法 然后去找target下面对应的类文件加载到(调用define方法()这个方法比较复杂,没有仔细研究过)jvm内存里面去 此时肯定加载不到因为类再target下面不在extclasss下面 返回为null 此时又会返回appclassload下面 找到对应appclassload的findclass()方法加载也会调用urlclassload的逻辑同时也会调用define()(调用一系列本地方法执行加载加载的过程)方法 此时就会加载到对应的.class文件

双亲委派源码: 重要的方法ClassLoad 类里面的loadClass 里面实现findClass()方法 真正加载类的逻辑

protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException
//  loadClass()实现双亲委派机制
{
    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;
    }
}

为什么要设计双亲委派机制?

  1. 沙箱安全机制:自己写的java.lang.String.class类不会被加载(包名和类名和jdk自带的一样 运行不成功),这样便可以防止核心API库被随意篡改(自己在自定义类加载器里面要加载java.lang....类似于和核心包里面系统的包名称 jvm直接报一个安全异常) jvm加载到和自己核心类库里面相同的类必须用自己类库里面的类。

  2. 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次(父类就直接返回了),保证被加载类的唯一性(相同包名类名只加载一份)。

类加载机制还有一个全盘负责委托机制,所谓的"全盘负责"是指当一个ClassLoder装载一个类时,假如这个类也用到其他的类了 这个类也会由这个ClassLoader加载,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。

相信很多人面试的时候一般都会问到关于双亲委派机制的问题。同时可能面试官还会问一个怎么打破这个机制。如果问到这个问题基本上就是问双亲委派的源码了,在上面的代码里面也提到了,双亲委派机制核心就是两个核心:ClassLoad 类里面的loadClass 里面实现findClass()方法 真正加载类的逻辑,基本上就是重写重写loadClass()方法即可。

为什么要打破双亲委派机制?

(双亲委派加载在以下情况下不能解决问题(tomcat为例) 1,假如web容器里部署两套应用程序,不同的应用程序可能依赖第三方库的版本不一样,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离 一个spring版本4 一个是spring5 如果4先加载了 如果5再使用同样的库 那么拿到的就是4加载的那个 就会出现问题,部署在同一个web容器中相同的类库相同的版本可以共享, 2, web容器也有自己依赖的类库,不能与应用程序的类库混淆。3,基于安全考虑,应该让容器的类库和程序的类库隔离开来。 这些问题是双亲委派机制解决不了的 双亲委派机制要求同一个类同一个包名只能加载一份)

tomcat就是典型的打破了双亲委派:

tomcat 为了实现隔离性 没有遵循jvm双亲委派机制 tomcat自定义了很多类加载器 其中的一个WebAppClassLoder 每一个应用程序webappClassLoader都是相互隔离的加载自己的目录下(jir)的class文件,不会传递给父类加载器,打破了双亲委派机制。tomcat里面每一个war包都会对应生成一个appClassLoad。

JAVA类加载机制详解_第1张图片

 

你可能感兴趣的:(java,jvm)