• java虚拟机详解篇六(类的加载机制)


    类的加载机制

    类的加载机制一共有四种,分别是全盘负责机制,父类委托机制,缓存机制,还有最重要的双亲委派机制

    全盘负责

    所谓全盘负责,就是说当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。

    父类委托

    所谓父类委托则是先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。

    缓存机制

    缓存机制将会保证所有被加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,并存入cache。这就是为什么我们修改了Class后,程序必须重新启动JVM,程序所作的修改才会生效的原因。(因为此时缓存中不存在修改后的Class对象)

    双亲委派机制

    java虚拟机对于class文件是按需加载也就是说需要该类时才会把它的class文件加载进内存里生成class对象,而且加载某个class文件时,Java虚拟机采用的是双亲委派机制,即把请求交由父类处理,它是一种任务委派模式。
    双亲委派机制的代码实现:

    public Class<?> loadClass(String name)throws ClassNotFoundException {
                return loadClass(name, false);
        }
        protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
                // 首先判断该类型是否已经被加载
                Class c = findLoadedClass(name);
                if (c == null) {
                    //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
                    try {
                        if (parent != null) {
                             //如果存在父类加载器,就委派给父类加载器加载
                            c = parent.loadClass(name, false);
                        } else {
                        //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                            c = findBootstrapClass0(name);
                        }
                    } catch (ClassNotFoundException e) {
                     // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                        c = findClass(name);
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    双亲委派机制的工作原理为:

    1. 当一个类加载器收到类加载请求,它不会自己先去加载,而是委派父类加载器去加载这个类。
    2. 如果父类加载器之上还存在父类加载器,则进一步委托,依次递归,最后到达顶层的引导类加载器(这个加载器是由C/C++书写的,一般是无法访问的。)
    3. 如果父类加载器可以成功加载这个类,则返回加载成功的数据,如果父类加载器无法加载,那子加载器会尝试加载这个类。

    双亲委派机制的过程:

    1. 当AppClassLoader(应用程序类加载器)加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
    2. 当ExtClassLoader(扩展类加载器)加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader(引导类加载器(根类加载器))去完成。
    3. 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
    4. 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

    具体过程如图所示:
    在这里插入图片描述

    实例代码:

    package GenericsTest.JVMTest;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    import java.io.IOException;
    import java.io.InputStream;
    
    public class ClassLoaderTest {
        public static void main(String[] args) throws Exception {
            ClassLoader myLoader = new ClassLoader() {
                @Override
                public Class<?> loadClass(String name) throws ClassNotFoundException {
                    try {
                        String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
                        InputStream is = getClass().getResourceAsStream(fileName);
                        if (is == null) {
                            return super.loadClass(name);
                        }
                        byte[] b = new byte[is.available()];
                        is.read(b);
                        return defineClass(name, b, 0, b.length);
                    } catch (IOException e) {
                        throw new ClassNotFoundException(name);
                    }
                }
            };
            System.out.println("parent ClassLoader: "+ myLoader.getParent());
            Class<?> clazz = myLoader.loadClass("GenericsTest.JVMTest.Main");
            System.out.println("class name: " + clazz.getSimpleName() + " \nclass hashcode: " + clazz.hashCode() + " \nloader: " + clazz.getClassLoader().getClass().getSimpleName());
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    结果: 可以看到的是打印出来的类加载器是APPClassLoader(应用程序类加载器),而非自定义的类加载器,为什么呢?这就涉及到了双亲委派机制,
    在这里插入图片描述
    当我们用 myLoader 去加载 GenericsTest.JVMTest.Main.java 这个类的时候,根据双亲委托机制,自定义的类加载器 myLoader 会委托它的父加载器 AppClassLoader 去加载, AppClassLoader 应用类加载器又会委托它的父类加载器 Bootstrap ClassLoader 启动类去加载。而 Bootstrap ClassLoader 找不到这个类,然后让 AppClassLoader 去加载,而 AppClassLoader 加载的路径是项目的 ClassPath, 这时候找到了 Main类并加载了它,并没有让 myLoader 去加载。

    双亲委托机制还有下面的一些特点:

    1. 如果没有显示地传递一个双亲类装载器给用户自定义的类装载器的构造方法,系统装载器就默认被指定为双亲。
    2. 如果传递到构造方法的是一个已有的用户自定义类型装载器的引用,该用户自定义装载器就被作为双亲。
    3. 如果传递的方法是一个 null, 启动类装载器就是双亲。
    4. 在类装载器之间具有了委派关系,首先发起装载要求的类装载器不必是定义该类的类装载器。

    沙箱安全机制

    这个机制主要是保护java源代码信息的。如果我们自己建立和源代码相同的包,例如创建类名相同的String类,在我们去使用类加载器去加载此类时,为了防止你自定义的类对源码的破坏,所以他默认不是使用你的String类的本身的系统加载器去加载它,而是选择率先使用引导类加载器去加载,而引导类在加载的过程中会先去加载JDK自带的文件(rt.jar包中的java/lang/String.class),而不是你自己定义的String.class,报错信息会提示没有main方法 ,就是因为加载的是rt.jar包下的String类,这样就可以做到保证对java核心源代码的保护,这就是沙箱保护机制。

    实例代码:

    package java.lang;
    
    /**
     * 报错:
     * 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
     *    public static void main(String[] args)
     * 否则 JavaFX 应用程序类必须扩展javafx.application.Application
     * 
     */
    public class String {
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结果分析: 这个程序可以运行,但是它不会加载当前的java.lang.String,而是会加载默认的java.lang.String包,在那个包下没有main方法,而当前程序需要运行main方法,因此会报错。

    文章参考

    • java全栈知识体系
    • http://www.cnblogs.com/ityouknow/p/5603287.html
    • http://blog.csdn.net/ns_code/article/details/17881581
    • https://segmentfault.com/a/1190000005608960
    • http://www.importnew.com/18548.html
    • http://zyjustin9.iteye.com/blog/2092131
    • http://www.codeceo.com/article/java-class-loader-learn.html
    • https://www.jianshu.com/p/fc7e935af99d
  • 相关阅读:
    主机ping不通虚拟机,虚拟机可以ping同主机
    【Vue五分钟】Vue项目的后期打包、上传、构建文档、组件测试操作
    【CentOS7】安装docker
    一款好看的markdown编辑器:md-editor-v3
    03、自定义镜像上传阿里云
    【代码仓库提交大文件,用Git LFS!】
    哈希表超详解
    SpringBoot WebService服务端&客户端使用教程
    C++ IO操作(0)输入输出库介绍
    组合学笔记(六)局部有限偏序集的关联代数,Möbius反演公式
  • 原文地址:https://blog.csdn.net/m0_46198325/article/details/126376476