美文网首页
Java类加载机制

Java类加载机制

作者: NeXt4 | 来源:发表于2021-01-03 13:58 被阅读0次

    字节码

    xx.java -> 编译器 -> cc.class -> JVM
    可以通过xxd xx.class命令查看字节码文件
    cafe babe 是JVM识别.class文件的标志,被称为“魔数”。

    类加载过程

    类加载的5个阶段:载入、验证、准备、解析、初始化
    一般这5个阶段是顺序发生的,但是在动态绑定的情况下,解析阶段发生在初始化阶段之后。

    1. Loading 载入

    JVM 将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。

    2. Verification 验证

    JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。

    验证阶段大致会完成4个阶段的检验动作:

    • 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
    • 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
    • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
    • 符号引用验证:确保解析动作能正确执行。

    验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

    3. Preparation 准备

    JVM 会在该阶段为类的静态变量分配内存,并将其初始化为默认值(对应数据类型的默认初始值,如 0、0L、null、false 等)。

    也就是说,假如有这样一段代码:

    public String chenmo = "沉默";
    public static String wanger = "王二";
    public static final String cmower = "沉默王二";
    

    chenmo 不会被分配内存,而 wanger 会;但 wanger 的初始值不是“王二”而是 null。把wanger赋值为“王二”的putstatic指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把赋值为“王二”的动作将在初始化阶段才会执行。

    需要注意的是,static final 修饰的变量被称作为常量,和类变量不同。
    常量一旦赋值就不会改变了,所以 cmower 在准备阶段的值为“沉默王二”而不是 null。

    4. Resolution 解析

    该阶段将常量池中的符号引用转化为直接引用

    符号引用以一组符号(任何形式的字面量,只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。

    在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如 com.Wanger 类引用了 com.Chenmo 类,编译时 Wanger 类并不知道 Chenmo 类的实际内存地址,因此只能使用符号 com.Chenmo。

    直接引用通过对符号引用进行解析,找到引用的实际内存地址。

    5. Initialization 初始化

    该阶段是类加载过程的最后一步。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。换句话说,初始化阶段是执行类构造器方法的过程。

    在Java中对类变量进行初始值设定有两种方式:
    ①声明类变量是指定初始值。
    ②使用静态代码块为类变量指定初始值。

    类加载器

    对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。

    Java 类加载器可以分为三种。
    1)启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar。
    2)扩展类加载器(Extension or Ext Class-Loader),加载 jre/lib/ext 包下面的 jar 文件。
    3)应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。

    public class Test {
        public static void main(String[] args) {
            ClassLoader loader = Test.class.getClassLoader();
            while (loader != null) {
                System.out.println(loader.toString());
                loader = loader.getParent();
            }
        }
    }
    

    双亲委派模型

    自定义类加载器 -> 应用程序类加载器 -> 扩展类加载器 -> 启动类加载器

    这种层次关系被称作为双亲委派模型:如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。

    使用双亲委派模型有一个很明显的好处,那就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。

    https://zhuanlan.zhihu.com/p/73078336
    https://zhuanlan.zhihu.com/p/25228545

    相关文章

      网友评论

          本文标题:Java类加载机制

          本文链接:https://www.haomeiwen.com/subject/chyuoktx.html