JVM 之 (13) 类加载机制——案例分析

  在《JVM 之 (12) 类加载机制》一文中详细阐述了类加载的过程,并举了几个例子进行了简要分析,在文章的最后留了一个悬念给各位,这里来揭开这个悬念。建议先看完《JVM 之 (12) 类加载机制》这篇再来看这个,印象会比较深入,如若否则,也没什么关系~~ 
下面是程序代码:
java

package jvm.classload;

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;
}

  问题是:请问这段程序的输出是什么? 
  这个是我在论坛上看到的一个问题,我以为比较经典。 
  通常对于这类问题,小伙伴们脑海中确定浮现出这样的knowledge:app

Java中赋值顺序: 
1. 父类的静态变量赋值 
2. 自身的静态变量赋值 
3. 父类成员变量赋值和父类块赋值 
4. 父类构造函数赋值 
5. 自身成员变量赋值和自身块赋值 
6. 自身构造函数赋值jvm

  ok,按照这个理论输出是什么呢?答案输出: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了。) 
  类的初始化阶段须要作是执行类构造器(类构造器是编译器收集全部静态语句块和类变量的赋值语句按语句在源码中的顺序合并生成类构造器,对象的构造方法是<init>(),类的构造方法是<clinit>(),能够在堆栈信息中看到),所以先执行第一条静态变量的赋值语句即st = new StaticTest (),此时会进行对象的初始化,对象的初始化是先初始化成员变量再执行构造方法,所以设置a为110->打印2->执行构造方法(打印3,此时a已经赋值为110,可是b只是设置了默认值0,并未完成赋值动做),等对象的初始化完成后继续执行以前的类构造器的语句,接下来就不详细说了,按照语句在源码中的顺序执行便可。 
  这里面还牵涉到一个冷知识,就是在嵌套初始化时有一个特别的逻辑。特别是内嵌的这个变量刚好是个静态成员,并且是本类的实例。 
  这会致使一个有趣的现象:“实例初始化居然出如今静态初始化以前”。 
  其实并无提早,你要知道java记录初始化与否的时机。 
  看一个简化的代码,把关键问题解释清楚:
spa

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

  根据上面的代码,有如下步骤:.net

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

  详细看到这里,心中大概有个结论了吧,若是对于类的加载机制比较模糊的话,能够参考开篇推荐的博文~ 有问题欢迎留言。


对象

相关文章
相关标签/搜索