类得生命周期
在讲类的加载机制前,我们都知道一个对象的生命周期指的是这个对象从创建到销毁的过程,这个国政简单的一句话概括:
从JVM将字节码文件加载进内存到卸载出内存为止。
它的整个生命周期包括:
其中准备、验证、解析3个部分统称为连接(Linking)。

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
假设一个类变量的定义为:publicstaticintvalue=3;
那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器 <clinit>()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。这里还有一下注意点:
<clinit>()方法的过程<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。如下:
public class Test{
static{
i = 0;
System.out.printin(i);// error,非法向前引用
}
static int i = 1;
}如果现在去掉错误的那句,改成如下,那么访问结果又是怎样?
public class Test{
static{
i = 0;
// System.out.printin(i);
}
static int i = 1;
public static void mian(string args[]){
System.out.println(i);
}
}在这里如果你的答案是0,那么恭喜你,你的类加载机制还需要看个四五遍。那么这里简单的分析这份字节码被加载进内存中类变量是如何初始化的。
为类变量设置初始化值主要是在准备和初始化阶段:
这里主要就是明白一点:假如类中有初始化语句,则系统依次执行这些初始化语句。
接下来我们在说下类的初始化时机:只有当对类的主动使用的时候(引用该类)才会导致类的初始化,类的主动使用包括以下六种:
这里说到了类的主动使用,拿什么是类的被动使用?
public class FinalTest{
public static void main(String[] args){
System.out.println(Final.a);
}
}
calss Final{
public static int a = 10;
static{
System.out.println(1);
}
{
System.out.println(2);
}
public Final{
System.out.println(3);
}
}结果:
10
我们把这段代码改一下
public class FinalTest{
public static void main(String[] args){
System.out.println(Final.a);
}
}
calss Final{
public static int a = 10;
static{
a = 100;
System.out.println(1);
}
{
a = 1000;
System.out.println(2);
}
public Final{
a = 1000;
System.out.println(3);
}
}结果:
1
10可以看出代码值经过了 Final类的 <clinit>() ,由此在Final中按顺序执行了类变量的初始化和static块的中变量的初始化。(这段断码并没有触发对象的初始化,因为按顺序执行并没有执行Final对象的new操作)接下来再把代码改一下:
public class FinalTest{
public static void main(String[] args){
System.out.println(new Final().a);
}
}
calss Final{
public static int a = 10;
static{
a = 100;
System.out.println(1);
}
{
a = 1000;
System.out.println(2);
}
public Final{
a = 10000;
System.out.println(3);
}
}结果
1
2
3
10000相信看到这里,大部分应该都明白这个 执行结果,创建对象时先执行顺序:
明白这个这段代码的执行结果就很好理解了。接下来再把这段代码改变一下:
public class FinalTest{
public static void main(String[] args){
System.out.println(Final.a);
}
}
calss Final{
public static Final2 f2 = new Final2();
public static int a = 10;
static{
a = 100;
System.out.println(1);
}
{
a = 1000;
System.out.println(2);
}
public Final{
a = 10000;
System.out.println(3);
}
}
calss Final2{
public static int b = -10;
static{
b = -100;
System.out.println(4);
}
{
b = -1000;
System.out.println(5);
}
public Final{
b = -10000;
System.out.println(6);
}
}结果
4
5
6
1
100这段代码经过了 Final类的 <clinit>(),在Final类中按顺序执行了Final2对象的初始化和类变量的初始化和static块的中变量的初始化。
在此本章说明类加载机制已经全部说完,集中类初始化时机也有所是举例,但是还有一种特俗的情况,见如下代码:
public class FinalTest{
public static void main(String[] args){
System.out.println(Final.a);
}
}
calss Final{
public static int a = 10;
public static Final f = new Final();
static{
a = 100;
System.out.println(1);
}
{
a = 1000;
System.out.println(2);
}
public Final{
a = 10000;
System.out.println(3);
}
}结果
2
3
1
100f静态变量的实例初始化嵌入到了静态(类)初始化流程中,并且在上面的程序中,嵌入到了静态(类)初始化的起始位置。这就导致了实例初始化完全发生在静态初始化之前。
补充说明:本文后半部分一直在介绍类得初始化,其实跟类得出事化相仿得还有对象得初始化,只不过一般情况类得初始化发生对象初始化之前(可以理解为:在类生命周期中类初始化之后是使用阶段,当在代码里中有new操作时就会发生对象得初始化操作,不过如上文提到也会有特俗情况)。
下面附上对象得初始化时机: