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