JVM 类的加载机制

版权声明:本文为博主原创文章,如需转载请注明原文出处: https://blog.csdn.net/u010979642/article/details/90723392

1. 类的加载机制

Java 虚拟机将 Java 类加载到内存的过程。

  1. Java 源文件 通过 编译 生成 class文件
  2. 类加载器 ClassLoader 会读取这个 .class 文件, 并将其转换为 java.lang.Class 的实例。 有了该实例, JVM 就可以用它来创建对象, 调用方法等操作了。

2. 类的加载过程

类从被加载到虚拟机内存中开始, 到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用以及卸载7个阶段, 其中验证、准备、解析3个阶段统称为连接。

3. 类与类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。因此, 比较两个类是否"相等", 只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

不同类加载器对 instanceof 关键字运算结果的影响

public class AnswerApp {

    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[] bytes = new byte[is.available()];
                    is.read(bytes);

                    return defineClass(name, bytes, 0, bytes.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };

        Object object = myLoader.loadClass(AnswerApp.class.getName()).newInstance();

        System.out.println(object.getClass());
        System.out.println(object instanceof com.aal.AnswerApp);
        
        System.out.println(AnswerApp.class.getClassLoader());
        System.out.println(object.getClass().getClassLoader());
    }
}

程序运行输出

class com.aal.AnswerApp
false
sun.misc.Launcher$AppClassLoader@18b4aac2
com.aal.AnswerApp$1@20ad9418

4. 类加载器

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

  • 启动类加载器BootStrap ClassLoader 负责将存放在 <JAVA_HOME>/lib 目录中的 或者 被 -Xbootclasspath 参数所指定的路径中的类库加载到虚拟机内存中。

  • 扩展类加载器Extension ClassLoader 负责加载 <JAVA_HOME>/lib/ext 目录中的 或者 被 java.ext.dirs 系统变量所指定的路径中的所有类库。

  • 应用程序类加载器Application ClassLoader 负责加载用户类路径(classpath)上所指定的类库, 如果应用程序没有自定义过自己的类加载器, 一般情况下这个就是程序中默认的类加载器。

5. 双亲委派模型

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

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

截至至今, 双亲委派模型出现过3次较大规模的"被破坏"情况。 第一次出现在 JDK1.2 发布之前, 因为双亲委派模型是在 JDK1.2 之后才被引入。而加载器和抽象类 java.lang.ClassLoader 则在 JDK1.0 时代就已经存在。第二次是由于这个模型自身的缺陷所导致的。第三次是由于用户对代码热替换,模块热部署的追求所导致的。具体信息请自行查阅相关资料了解。

猜你喜欢

转载自blog.csdn.net/u010979642/article/details/90723392