JVM把描述类的数据从Class文件加载到内存,对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是JVM类加载机制。
Java的类型加载、连接和初始化过程都是在程序运行期间完成的。
类加载到内存,和到卸载出内存为止,整个生命周期:加载、验证、准备、解析、初始化、使用和卸载,等7个阶段,验证、准备、解析3个部门统称为连接。生命周期如下:
对类进行初始化的5种情况:
1.遇到new、getstatic、putstatic、或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类么有进行过初始化,则需要先触发其初始化。
3.当初始化类的时候,如果发现其父类还没有初始化,则必须先触发其父类的初始化。
4.虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
5.当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个放啊放句柄所对应的类还没有进行初始化,则需要先触发其初始化。
除了以上5中方式外,主所有的引用类的方法都不会触发初始化,称为被动引用。
package cn.edu.hust.jvm;
public class SuperClass {
static
{
System.out.println("父类被初始化");
}
public static int value=123;
}
package cn.edu.hust.jvm;
public class SubClass extends SuperClass{
static
{
System.out.println("子类初始化");
}
}
package cn.edu.hust.jvm;
public class MainTest {
public static void main(String[] args)
{
System.out.println(SubClass.value);
}
}
以上代码答应出来的结果是
这里并没打印出子类初始化等信息,主要的原因是针对于静态字段,只有直接定义这个字段的类才会被初始化。
下面是被动引用的例子:
package cn.edu.hust.jvm;
public class MainTest {
public static void main(String[] args)
{
System.out.println(SubClass.value);
SubClass[] tt=new SubClass[10];
}
}
这里为了区分前面的类,使用的是子类创建出数组来查看结果。
结果也并没有答应出子类初始化等信息,这是因为并没有触发子类的初始化。
但是这里出发了一个继承object类,由newarray创建的类。
被动触发的例子三:
package cn.edu.hust.jvm;
public class Constant {
static
{
System.out.println("constant init");
}
public static final String s="hello world";
}
package cn.edu.hust.jvm;
public class MainTest {
public static void main(String[] args)
{
// System.out.println(SubClass.value);
// SubClass[] tt=new SubClass[10];
System.out.println(Constant.s);
}
}
上述例子打印的结果是:
这里打印的结果并没有“Constant init”,这是因为符合初始化规则1,final修饰的字段在编译阶段就会存入调用到类的常量池。
也就是说Constan的s字段,将会被存储在MainTest的常量池中。
针对于接口的初始化,需要注意的是:
当一个类在初始化时,要求其父类全部都已经初始化过,但是一个在初始化时,并不要求其父类接口全部都完成初始化,只有在真正使用父借口时,才会初始化。