美文网首页程序员
解读面试题,彻底搞懂类加载和初始化顺序

解读面试题,彻底搞懂类加载和初始化顺序

作者: AI码师 | 来源:发表于2020-09-19 22:49 被阅读0次

在高级面试过程中,始终逃不过面试官的追问三连:

  • 你知道jvm是怎么加载类的么?
  • 类的初始化顺序你有了解么?
  • 我出一个面试题,你能答出来么?

三连问下来,恐怕自己已经被劝退了,有的同学肯定会学过jvm是如何加载类的,但是被面试官一问,就一脸懵了,主要原因是没有掌握到精髓,不知道其中的原理,光靠死记硬背是不行的,面试官都看在眼里的。本文将带着大家一起分析面试题,来梳理下其中主要的知识点,相信大家在看完之后,一定会有所收获的,可以彻底告别面试官的连连追问了。

面试题1

现在我们进入正题:
面试官直接抛过来第一道面试题,看看大家能猜出结果不

package org.apache.dubbo.demo.provider;

public class JVMClass extends BaseCodeBlock {
    {
        System.out.println("子类的普通代码块");
    }
    public JVMClass() {
        System.out.println("子类的构造方法");
    }
    @Override
    public void msg() {
        System.out.println("子类的普通方法");
    }

    public static void msg2() {
        System.out.println("子类的静态方法");
    }

    static {
        System.out.println("子类的静态代码块");
    }

    public static void main(String[] args) {
        BaseCodeBlock bcb = new JVMClass();
        bcb.msg();
    }
    Other o = new Other();
}

class BaseCodeBlock {

    public BaseCodeBlock() {
        System.out.println("父类的构造方法");
    }

    public void msg() {
        System.out.println("父类的普通方法");
    }

    public static void msg2() {
        System.out.println("父类的静态方法");
    }

    static {
        System.out.println("父类的静态代码块");
    }

    Other2 o2 = new Other2();

    {
        System.out.println("父类的普通代码块");
    }
}

class Other {
    Other() {
        System.out.println("初始化子类的属性值");
    }
}

class Other2 {
    Other2() {
        System.out.println("初始化父类的属性值");
    }
}

大家可以把上面的代码拷贝到编辑器上面,执行下看看和自己预期的结果是否一致,这段代码基本上可以展示出了类加载和初始化顺序,给大家看下结果


image.png

可以看出如果有继承父类的话,会优先去初始化父类。遵循这样一个顺序

image.png

父类的静态代码块->子类的静态代码块->初始化父类的属性值/父类的普通代码块(按照代码的顺序排列执行)->父类的构造方法->初始化子类的属性值/子类的普通代码块(按照代码的顺序排列执行)->子类的构造方法。
构造方法最后才执行。

面试题2

面试官开始出第二题了,又抛来一段代码,细品:

package com.example.demo;

public class JVMClass2 {
    public static void main(String[] args) {
        Singleton1 s1 = Singleton1.getSingleton();
        Singleton2 s2 = Singleton2.getSingleton();
        System.out.println("s1:counter1 = "+ s1.counter1);
        System.out.println("s1:counter2 = "+s1.counter2);
        System.out.println("s2:counter1 = "+ s2.counter1);
        System.out.println("s2:counter2 = "+s2.counter2);
    }
}
class Singleton1{
    private static Singleton1 singleton = new Singleton1();
    public static int counter1;
    public static int counter2 = 0;
    public Singleton1(){
        counter1++;
        counter2++;
    }

    public static Singleton1 getSingleton(){
        return singleton;
    }
}
class Singleton2{
    public static int counter1;
    public static int counter2 = 0;
    private static Singleton2 singleton = new Singleton2();
    public Singleton2(){
        counter1++;
        counter2++;
    }

    public static Singleton2 getSingleton(){
        return singleton;
    }
}

大家可以开动脑经,结合上面的加载顺序,来分析分析这道题的答案。
我们直接看运行结果


image.png

是不是和内心预期的有点出入啊,我们按照上面的思路分析下,

  1. 构造器是在所有初始化之后才执行的
  2. 那么在所有属性初始化之后,在执行构造器的时候,counter1和counter2都会有默认的初始值0,那么执行完构造器之后,counter1和counter2结果肯定都是1,
  3. 如果这样的话,Singleton2和Singleton1的执行结果应该是一样的,那么为什么Singleton2和我们的预期一致,Singleton1却有出入呢
  4. 我们看到他们俩唯一的区别是进行new 实例化的位置不一样。
  5. 既然找到了不同,我们继续往下分析,我们主要分析和我们预期不一致的Singleton1。
  6. Singleton1 实例化是在静态变量位置上面,所有优先他们执行,所以进入构造器执行,同学们可能会有疑问了,上面不是说构造器是在最后才执行么,为什么这里会先执行呢?因为这个构造器执行是有实例化代码触发的,所以会进行内部递优先执行构造方法
  7. 进入构造放方法,这时候counter1和counter2还是保持零值(这是有虚拟机加载class准备阶段执行的,后面会说),执行++之后,counter1和counter2都会变成1。构造方法执行结束,进入下一步
  8. counter1没有被显示赋值,counter2被重新赋值为0,到此初始化结束。
  9. 得到counter1和counter2分别为1和0

到此,面试已经结束,我们现在来结合理论知识来总结下jvm类加载和初始化顺序。

java类加载分为五个过程,如图:

image.png

类加载顺序

加载

这阶段主要是加载class文件到JVM中,class文件可以是来自本地,也可以是一段二进制流:jvm主要做了如下工作:
1)通过classloader 获取XXX.class文件,将其以二进制流的形式读入内存。
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

这阶段主要是为了确保加载的class 文件能够符合JVM规范,特别是来自网络的二进制字节流,必须要经过校验,防止发生不可预知的错误,当然如果你的确信你的class文件是经过多次测试没有任何问题的话,是可以使用参数关闭验证的,感兴趣的可以了解下。这阶段主要进行了如下内容的验证:

  1. 文件格式的验证:

    验证文件格式是否按照虚拟机的规范,也就是我们前面class文件结构中的内容,比如这是不是一个Class文件(看魔数,是否位CAFEBABE);

    Java版本是否符合当前虚拟机的范围(Java可以向下兼容,但是不能处理大于当前版本的程序)等等。

  1. 元数据的验证:

    对Class文件中的元数据进行验证,是否存在不符合Java语义的元数据信息。这里有的朋友可能会比较疑惑,什么是元数据呢?一般情况下,一个文件中都数据和元数据。数据指的是实际数据,而元数据(Metadata)是用来描述 数据的数据。用过Java注解的朋友应该对元数据这种叫法并不陌生,对应的元注解,其实说的差不多都是一个意思。

    举个例子:比如说我们定义了一个变量 int a = 1;可以理解成数据就是1,而元数据就是描述有一个字符串变量“a”,这个“a”的类型是int型的,它的值也是一个int型的1,这就是描述数据的数据,就是元数据。

  1. 字节码的验证:

    通过数据流和控制流分析,来确定程序语义是否合法。

    以数据来说,要保证类型转换是有效的;对于控制流程的代码,不能让指令跳转到其它方法的字节码指令上等……

  1. 符号引用的验证:

    为了保证解析动作能正常完成,还需在虚拟机将符号引用转成直接引用的时候,判断其它要引用的类是否符合规定。比如,要引用的类是否能够被找到;引用的属性在对应类中是否存在,权限是否符合要求(private的是不能访问 的)等。

准备

准备阶段是用来为静态变量在方法区分配内存,并设置零值,上面有提到过,可以串联起来理解。

解析

将虚拟机常量池内的符号引用解析为直接引用,指到内存中的具体地址。

初始化

这一步才正真开始执行我们的代码,进行变量的赋值和相应初始化操作,如果有继承关系,则优先初始化父类。

使用

就是使用。。。

卸载

jvm会将对象标记为null,等待垃圾回收器进行GC

了解完加载的五大步之后,我们再看下最后一个重要的知识点,就是我们使用类的时候,什么时候会触发类的初始化呢,分为以下四种情况:

什么时候会进行类的初始化

  1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。
  3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
  4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
  5. 当使用 JDK1.7 的动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个句柄没有初始化,则需要先触发器初始化。

今天分享的面试内容到此结束,我们下一期再见吧。

微信搜一搜【乐哉开讲】关注帅气的我,回复【干货】,将会有大量面试资料和架构师必看书籍等你挑选,包括java基础、java并发、微服务、中间件等更多资料等你来取哦。

书读的越多而不加思考,你就会觉得你知道得很多;而当你读书而思考得越多的时候,你就会越清楚地看到,你知道得很少。——伏尔泰

相关文章

  • 解读面试题,彻底搞懂类加载和初始化顺序

    在高级面试过程中,始终逃不过面试官的追问三连: 你知道jvm是怎么加载类的么? 类的初始化顺序你有了解么? 我出一...

  • JVM类加载入门

    一 类加载顺序 class类加载-->验证-->准备--->解析--->初始化 class类加载:通过类加载器加载...

  • 类初始化和加载过程

    一、对象的初始化顺序: java类加载器加载类的顺序:http://blog.csdn.net/crazycode...

  • 类加载机制

    目录 概念 加载过程 初始化时机 类初始化顺序注意点 双亲委派模型 自定义类加载器 类加载 概念 Java虚拟机把...

  • JVM的类加载机制

    类的生命周期 其中,加载,验证,准备,初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序开始,而类...

  • 类的加载过程

    类的加载过程 类文件加载的顺序 1、先加载执行父类的静态变量及静态初始化块(执行先后顺序按排列的先后顺序)2、再加...

  • Java类加载机制

    一、加载过程 固定的顺序:加载、【验证、准备、解析】、初始化和卸载; 加载:类加载过程的一个阶段:通过一个类的完全...

  • JVM类加载器

    类加载阶段 加载 连接(验证、准备、解析) 初始化 使用 卸载解析和初始化的相对顺序不确定,为了支持动态绑定,解析...

  • Java类加载与初始化

    今天复习了一下java类加载时的初始化顺序,先记录一下心得,代码后续补上。 类加载时的初始化顺序 1、一般来说,类...

  • JAVA的类加载顺序

    理解关于java类的加载顺序 首先来捋一捋java类的加载顺序吧 父类静态代码块 》》静态代码初始化,静态属性(带...

网友评论

    本文标题:解读面试题,彻底搞懂类加载和初始化顺序

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