Java中的类加载器

正文

类加载是由什么完成的?类加载器,通过一个类的全限定名来获取描述该类的二进制字节流。那么,它在加载类的时候是怎么保证不重复加载类?防止核心类被加载替换成别的类?

类加载器

分类

  • 启动类加载器(BootStrap Class Loader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等。
  • 扩展类加载器(Extension Class Loader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。
  • 应用程序类加载器(Application Class Loader):负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类。
  • 自定义类加载器(User Class Loader):用户自定义类加载器,负责加载用户自定义路径下的类包。
    如果站在虚拟机的角度,只能分成两类:一类是启动类加载器,它是由 C++ 语言实现,是虚拟机自身的一部分;另一类是其它所有的类加载器,都由 Java 语言实现。

双亲委派机制

双亲委派机制的基本概念

双亲委派机制长啥样了?


双亲委派机制模型

需要说明的是,他们是没有继承关系的,更多的是一种组合的关系。
大概流程:有一个程序猿编写的类 A,最先会使用应用类加载器,应用类加载器会委托扩展类加载器,扩展类加载器再委托启动类加载器加载,启动类加载器在自己的类加载路径翻了翻,没找到这个类,然后回复扩展类加载器没找到,扩展类加载器收到消息,也会在自己的加载路径中找对应的全路径名,半天没找到,继续向下退回给应用类加载器,它也在子级的加载路径找,找到以后,就自己加载。
流程总结来讲:子类加载器会先调用父类加载器加载,如果父类加载器没有加载到,再使用子类加载器加载。但是,这样有个问题?为什么不直接从启动类加载器开始加载,反而从应用程序类加载器开始加载了?我看的视频中,有一位老师说过一个可能,因为真实开发中,90%以上的类都是由应用程序类加载器加载,所以即使不从启动类加载器加载,也不会浪费很多性能。我推测有可能是在整个设计上的考虑,知道的小伙伴欢迎留言。

双亲委派机制的优点

  • 沙箱安全机制:可以防止核心 API 库被随意修改,可以自己写个 java.lang.String.java,看看会不会加载(最好新建一个项目测试,否则编译有可能会不通过)。
  • 避免类的重复加载:当父类已经加载了该类,子类加载器不会再进行加载,保证被加载类的唯一性。

核心实现代码

代码位于:java.lang.ClassLoader.loadClass(String name, boolean resolve)

synchronized (getClassLoadingLock(name)) {
            //第一步,判断这个类有没有被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {      //如果父类加载器不为空,使用父类加载器调用loadClass
                        c = parent.loadClass(name, false);
                    } else {                        //启动类加载器在 java 中是 null
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    //如果父类加载器没有加载到,抓住异常
                }

                if (c == null) {
                    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();
                }
            }

双亲委派机制的打破

其实,我们只需要重写上面方法即可。

@Override
        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) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //只有自定义的类才用自定义的findClass
//
                    if (name.startsWith("com.mtt.jvm.classload.User1")){
                        c = findClass(name);
                    }else {
                        c = this.getParent().loadClass(name);
                    }
                    //路径名需要自己指定
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

额外说明一点:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。这是我从别的资料 Copy 过来的,我对这个理解是,因为不同加载器的加载路径全路径是不一样的,所以相同包名和类名对象是可以共存的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。