生命周期
一个类型的生命周期包括:装载,连接,初始化的初始阶段,占生命周期大部分时间的类型实例化,垃圾收集和对象终结。及生命周期结束从虚拟机卸载该类型。
类加载机制
启动任何一个java程序,虚拟机从加载一个类开始会自动执行装载,连接,初始化这三步,因此开始阶段的这三个过程也称为类加载机制。
类加载机制的装载:
1、虚拟机通过需要加载的类的全限定名来定位对应的class文件,并以线性二进制字节流形式将它们加载到虚拟机。
2、虚拟机将这些字节流转化为方法区的内部数据结构(字节流代表的静态存储结构转化为方法区的运行时数据结构)。
3、在堆内存中生成代表各个类型的java.lang.Class类型的实例对象(此实例对象是反射机制创建对象的基础),作为方法区那些类的各种数据的访问入口(类在方法区,对象在堆)。由此可见方法区和堆在装载阶段就开始被分配和使用。一个虚拟机实例只有一个方法区和堆,被该虚拟机实例要运行的java程序的所有线程共享。
装载图——
类加载机制的连接:
1、验证——确保类文件的字节流包含信息符合当前虚拟机的规范。
2、准备——在方法区内给类变量分配内存,并给类变量设置默认初始值。
3、解析——将常量池内的符号引用(编译期间还未进行内存分配,只能用符号引用来代替实际引用)替换成直接引用。
类加载机制的初始化:(给类变量赋予正确的初始值)
1、先初始化类的直接超类(第一个被初始化的类永远是Object)(接口初始化时不需要管父接口)
2、执行本类的<clinit>方法(按代码顺序组织起来的类变量赋值语句和静态初始化块)
只有在初始化阶段,虚拟机才开始执行字节码。
使用阶段:
类型实例化:虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,然后检查这个符号引用代表的类是否已被加载,连接(不要求类初始化)。如果已经加载,连接,不管类是否初始化,则进行实例化。如果没有则先执行类加载的三个自然过程(包括类初始化),等三阶段的最后阶段类初始化执行完之后再进行类实例化。类加载检查通过后将给新生对象分配内存,分配到空间后将空间初始化为0值(不包括对象头),这一步操作保证了实例字段在程序中可以不赋初始值就可以使用。接下来虚拟机对对象进行必要的设置如此对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息被保存在对象头中。之后执行实例变量赋值语句或初始化块(按代码顺序执行)。最后执行<init>方法,把对象按程序员的意愿进行初始化。(对象的内存布局分为三部分:对象头,实例数据,对齐填充)从赋默认初始值0到执行构造方法,每创建一个对象都会执行一次。(类变量只初始化一次)
关于构造方法的执行:java编译器对某个类进行编译时,针对源代码中每个类的构造方法,编译器都产生一个<init>方法。如果类没有明确声明任何构造方法,编译器默认生成一个无参数构造方法,用于调用超类的无参数构造方法。(每个类至少有一个用来调用父类无参数构造方法的无参构造方法)。
子类实例化前一定先走一遍父类实例化的全过程(实例变量赋默认值,执行程序中实例变量赋值语句、执行初始化块,执行构造函数),父类实例化过程的最后一步执行父类的构造函数——取决于子类构造函数中super()的参数类型,如果没有super(),则执行父类的无参数构造函数。跟this()无关。
public class NewTest {
public int m=9;
public static int i;
public static NewTest d=new NewTest();
{
System.out.println("初始块执行"+"i="+i);
System.out.println("m的值"+m);
}
public static int j=m();
public static int mx(){
System.out.println("实例变量赋值语句先执行");
return 1;
}
public static int m(){
System.out.println("类方法执行");
return 1;
}
public NewTest(){
System.out.println("构造函数执行");
}
public int x=mx();
static{
System.out.println("静态初始块执行");
}
public static void main(String[] str){
NewTest ss=new NewTest();
}
}
//1类加载连接 2类初始化 3类实例化
//1类加载连接: i=0,d=null,j=0
//2类初始化:d=new NewTest(),j=m(),static{System.out.println("父类静态初始块执行");}
//(m=0,x=0|m=9,初始块执行i=0,m的值9,实例变量赋值语句先执行|父类构造函数执行。
//类方法执行
//父类静态初始块执行
//3类实例化: 初始块执行i=0,m的值9,实例变量赋值语句先执行,父类构造函数执行。
1.程序执行main()方法,即执行new NewTest();
2.检查NewTest是否已被加载连接和初始化。
3.如已连接:立即类实例化,1)给实例变量分配空间,赋默认初始值。2)按代码顺序执行实例变量赋值语句,初始化块。3)执行构造函数。
如未连接则先进行类加载,连接(给类变量(包括引用变量)分配空间,赋默认初始值)。然后类初始化(按代码顺序执行类变量赋值语句,静态初始化块),最后类实例化。
上面的例子中即是从类加载开始,具体的变化情况为:
一: 类加载连接:给类变量赋默认初始值 i=0,d=null,j=0
二: 类初始化: 给类变量赋程序设计的值或执行静态初始块 d=new NewTest(m=0,x=0(给实例变量分配空间及赋默认初始值);m=9,初始块执行i=0,m的值9,实例变量赋值语句先执行,x=1;构造函数执行),类方法执行,静态初始块执行
三: 类实例化: ss=new NewTest()
综上:
类实例化时遵循四步:加载连接初始化父类,加载连接初始化子类。父类实例化全过程走一遍(除了没有创建引用指向父类),子类实例化。
如果类初始化过程中需要进行类实例化,检查需要实例化的类是否已连接,如已连接:立即类实例化。
执行顺序:(“/”表示具体由顺序决定)
父类:静态变量/静态初始化块。
子类:静态变量/静态初始化块。
父类:变量/初始化块。
父类:构造函数。
子类:变量/初始化块。
子类:构造函数。