• 哥们,你真以为你会做这道JVM面试题?


    有关Java虚拟机类加载机制相关的文章一搜一大把,笔者这里也不必再赘述一遍了。

    笔者这里捞出一道code题要各位大佬来把玩把玩,如果你一眼就看出了端倪,那么恭喜你,你可以下山了:

    public class StaticTest
    {
       public static void main(String[] args)
       {
           staticFunction();
       }
       static StaticTest st = new StaticTest();
       static
       {
           System.out.println("1");
       }
       {
           System.out.println("2");
       }
       StaticTest()
       {
           System.out.println("3");
           System.out.println("a="+a+",b="+b);
       }
       public static void staticFunction(){
           System.out.println("4");
       }
       int a=110;
       static int b =112;
    }
    

    问题:请问这段程序的输出是什么?

    一般对于这类问题,小伙伴们脑海中肯定浮现出这样的知识点:

    Java中赋值顺序:
    • 父类的静态变量赋值
    • 自身的静态变量赋值
    • 父类成员变量赋值和父类块赋值
    • 父类构造函数赋值
    • 自身成员变量赋值和自身块赋值
    • 自身构造函数赋值

    按照这个理论输出是什么呢?答案输出:1 4,这样正确嚒?

    肯定不正确啦,这里不是说上面的规则不正确,而是说不能简单的套用这个规则。 

    正确的答案是:

    2
    3
    a=110,b=0
    1
    4
    

    有没有答对呢?这里主要的点之一:实例初始化不一定要在类初始化结束之后才开始初始化。 

    类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载。

    只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值,因此只针对这两个阶段进行分析;

    类的准备阶段需要做是为类变量分配内存并设置默认值,因此类变量st为null、b为0;

    需要注意的是如果类变量是final,编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将变量设置为指定的值。

    如果这里这么定义:static final int b=112,那么在准备阶段b的值就是112,而不再是0了。

    类的初始化阶段需要做的是执行类构造器。

    类构造器是编译器收集所有静态语句块和类变量的赋值语句,按语句在源码中的顺序合并生成类构造器,对象的构造方法是(),类的构造方法是(),可以在堆栈信息中看到。

    因此,先执行第一条静态变量的赋值语句,即st = new StaticTest (),此时会进行对象的初始化。

    对象的初始化是先初始化成员变量,再执行构造方法。因此打印2->设置a为110->执行构造方法(打印3,此时a已经赋值为110,但是b只是设置了默认值0,并未完成赋值动作)。

    等对象的初始化完成后,继续执行之前的类构造器的语句。接下来就不详细说了,按照语句在源码中的顺序执行即可。

    这里面还牵涉到一个冷知识,就是在嵌套初始化时有一个特别的逻辑。特别是内嵌的这个变量恰好是个静态成员,而且是本类的实例。

    这会导致一个有趣的现象:“实例初始化竟然出现在静态初始化之前”。 

    其实并没有提前,你要知道java记录初始化与否的时机。看一个简化的代码,把关键问题解释清楚:

    public class Test {
       public static void main(String[] args) {
           func();
       }
       static Test st = new Test();
       static void func(){}
    }
    

    根据上面的代码,有以下步骤:

    1. 首先在执行此段代码时,首先由main方法的调用触发静态初始化。
    2. 在初始化Test 类的静态部分时,遇到st这个成员。
    3. 但凑巧这个变量引用的是本类的实例。
    4. 那么问题来了,此时静态初始化过程还没完成就要初始化实例部分了。是这样么?
    5. 从人的角度是的。但从java的角度,一旦开始初始化静态部分,无论是否完成,后续都不会再重新触发静态初始化流程了。
    6. 因此在实例化st变量时,实际上是把实例初始化嵌入到了静态初始化流程中,并且在楼主的问题中,嵌入到了静态初始化的起始位置。这就导致了实例初始化完全至于静态初始化之前。这也是导致a有值b没值的原因。
    7. 最后再考虑到文本顺序,结果就显而易见了。

    相信看到这里,心中大概有个结论了吧。

  • 相关阅读:
    vi命令
    linux pip国内镜像修改
    LDA数学八卦笔记(二)Beta/Dirichlet分布
    不经风雨不见彩虹(个人作业——软件工程实践总结&个人技术博客)
    Java FX前端开发与测试
    个人作业——软件评测
    福大周润发队——团队作业4:系统设计和数据库设计
    福大周润发队——团队作业3:需求分析
    福大周润发队——团队作业2:github编程实战
    结对作业二——顶会热词统计的实现
  • 原文地址:https://www.cnblogs.com/hulianwangjiagoushi/p/10549164.html
Copyright © 2020-2023  润新知