前面给大家讲解了 Java 虚拟的内存结构 以及 Java 虚拟机的垃圾回收机制,我们更加明白了 Java 的内存管理机制,今天我们来讲讲 Java 虚拟机的另外一个高频考点:类加载机制。
JVM 的类加载过程分为加载、验证、准备、解析、初始化 5 个阶段。
加载
加载阶段由类加载器进行负责,类加载器根据一个类的全限定名读取该类的二进制字节流到 JVM 内部,然后转换为一个对应的 java.lang.Class 对象实例;一个类由类加载器和类本身一起确定,所以不同类加载器加载同一个类得到的 java.lang.Class 也是不同的。
验证
验证阶段负责验证类数据信息是否符合 JVM 规范,是否是一个有效的字节码文件。
准备
准备阶段是正式为类变量(static 修饰的变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区进行分配。
这个阶段由两个容易产生混淆的概念需要强调一下,首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。其次这里所说的初始值在没有被 final
修饰的时候都是数据类型的零值,只有类似 public static final int value = 1024
这样的情况下才回直接被赋值 123。
解析
解析阶段是虚拟机将常量池内的「符号引用」替换为「直接引用」的过程。
初始化
初始化阶段负责将所有的 static 域按照程序指定操作对应执行(赋值 static 变量,执行 static 块)。
上述阶段通常都是交叉混合允许,没有严格的先后执行顺序。
双亲委派模型
站在 Java 虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器,这个类加载器使用 C++ 语言实现,是虚拟机自身的一部分,另外一种是所有其他的类加载器,这种类加载器都由 Java 语言实现,独立于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader
。
双亲委派模型并不是一个强制性的约束模型,而是 Java 设计者们推荐给开发者们的类加载器实现方式。它的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的类加载器中,只有当父加载器反馈自己完成这个加载请求的时候,子加载器才会尝试自己去加载。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。
采用双亲委派模型的原因
比如黑客定义一个 java.lang.String 类,该 String 类和系统 String 类有一样的功能,只是在某个方法比如 equels() 中加入了病毒代码,并且通过自定义类加载器加入 JVM 中,如果没有双亲委派模型,那么 JVM 就可能误以为黑客编写的 String 类是系统 String 类,导致「病毒代码」最终被执行。而有了双亲委派模型,黑客定义的 java.lang.String 类就用于不会被加载进内存,因为最顶端的类加载器会加载系统的 String 类,最终自定义的类加载器无法加载 java.lang.String 类。
可以通过重写 loadClass() 方法,打破双亲委派模型。
最近的知识比较枯燥,但还是我们所必须了解的。
参考文献:《深入理解 Java 虚拟机》