Java:普通代码块,构造代码块,静态代码块区别及其执行顺序
类加载的机制过程分为以下:加载、验证、准备、解析、初始化等。
本文主要分析类的初始化过程。
类的初始化阶段,是真正开始执行类中定义的java程序代码(.class字节码),并按程序员的意图去初始化类变量的过程。更直接地说,初始化阶段就是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块static{}中的语句合并产生的,其中编译器收集的顺序是由语句在源文件中出现的顺序所决定。
关于类初始化的顺序:
- 静态变量、静态初始化块:取决于它们在类中出现的先后顺序 。
- 变量、初始化块:取决于它们在类中出现的先后顺序。
- 构造器
关于类初始化的详细过程,参见 Java虚拟机规范一书中,其中类初始化过程如下:
每个类都有一个初始化锁LC,进程获取LC,这个操作会导致当前线程一直等待,直到获取到LC锁。
- 如果Class正在被其他线程初始化,当前线程会释放LC,进入阻塞状态,并等待Class初始化完成。此时当前线程需要重试这一过程。执行初始化过程时,线程的中断状态不受影响。
- 如果Class正在被本线程初始化,即递归初始化,释放LC并且正常返回。
- 如果Class已经被初始化完成,释放LC并且正常返回。
- 如果Class处于错误状态,表明不可能再完成初始化,释放LC并抛出异常NoClassDefFoundError异常。
- 否则,将Class标记为正在被本线程初始化,释放LC;然后,初始化那些final且为基础类型的类成员变量。
- 从Class的ClassLoader处获取assertion断言机制是否被打开。
- 接下来,按照文本顺序执行类变量初始化和静态代码块,或接口的字段初始化,把它们当作是一个个单独的代码块。如果执行正常,那就获取LC,标记Class对象为已初始化,并通知所有正在等待的线程,然后释放LC,正常退出整个过程。
如果Class是类而不是接口,且Class的父类Super Class(SC)和各个接口SIn(按照implements子句中的顺序来)还没有初始化,那么就在SC上面递归地进行完整的初始化过程,如果有必要,需要先验证和准备SC ;如果SC或SIn初始化过程中抛出异常,则获取LC,将Class标记为错误状态,并通知所有正在等待的线程,然后释放LC,然后再抛出同样的异常。
如果过程中,抛出了异常E,那么会中断退出。若E不是Error,则以E为参数创建新的异常ExceptionInInitializerError作为E。如果因为OutOfMemoryError导致无法创建ExceptionInInitializerError,则将OutOfMemoryError作为E。获取LC,将Class标记为错误状态,通知所有等待的线程,释放LC,并抛出异常E。