1 何时进行类的初始化(当且仅当,其他各个时候都不进行初始化)
- 创建类的实例
- 访问类的静态变量和静态方法(如果是final的,也不初始化,因为在编译阶段编译器会把常量放到常量池里面)
- 反射 如(Class.forName("ClassA"))
- 初始化一个子类的时候如果父类尚未初始化,则先发出父类的初始化
- 虚拟机启动的时候,定义了main方法的那个类首先初始化
- 使用JDK7 对动态语言支持,java.lang.invoke包里面的MthodHandler等类时
以下情况不会进行类的初始化:
- 在子类中访问父类的静态变量的时候,不会进行子类的初始化。
- 进行数组定义的时候引用类,不会进行该类的初始化
比如A[] arr = new A[10];
不会初始化A
2 类的加载过程
-
加载
加载阶段虚拟机需要完成三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口 -
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
不同的虚拟机,对类验证的实现可能有所不同,但大致都会完成下面四个阶段的验证 :文件格式验证、元数据验证、字节码验证和符号引用验证。 -
准备
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
java
public static int value=123;
//在准备阶段value初始值为0 。在初始化阶段才会变为123 。
4. *解析*
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
**符号引用**(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
**直接引用**(Direct Reference):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。
5. *初始化*
类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的 。
###3 类的初始化顺序
对于类中的静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序以此是(静态变量、静态初始化块)>(变量、初始化块)>构造器。
对于继承,并不是父类完全初始化完毕后才进行子类的初始化,实际上子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。
###4 JVM类加载器
当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:
- bootstrap classloader
- extension classloader
- system classloader
*bootstrap classloader* -引导(也称为原始)类加载器,它负责加载Java的核心类。
*extension classloader* -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。
*system classloader* -系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。
JVM在加载类时默认采用的是**双亲委派**机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
类加载的双亲委派机制可以保证核心Java 类不会被篡改,保证同一类型在JVM中唯一的结构,也能保证JVM中装载的类的安全性,防止被篡改。