如上图所示,Java类的生命周期如图所示,分别为加载、验证、准备、解析、初始化、使用、卸载。其中验证、准备、解析这三个步骤统称为连接。java
加载:JVM根据全限定名来获取一段二进制字节流,将二进制流转化为方法区的运行时数据结构,在内存中生成一个表明该类的Java.lang.Class对象,做为方法区这个类的各类数据访问入口。数据结构
验证:验证是连接的第一步,主要验证内容分为文件格式验证、元数据验证、字节码验证、符号引用验证。spa
准备:准备阶段是为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。线程
解析:解析阶段是虚拟机将常量池的符号引用替换为直接引用的过程。符号引用所引用的目标不必定在内存中,而转换为直接引用以后,引用直接指向到对应的内存地址。设计
初始化:初始化会执行<Clinit>()方法,会对static属性进行赋值,对于static final修饰的基本类型和String类型则在更早的javac编译的时候已经加载到常量池中了。3d
通过初始化以后,Java类已经加载完毕。code
JVM并无强制规定何时进行类的加载,可是对于初始化规定了有且5种状况必须被初始化:对象
常在笔试题中遇到的就是类记载相关知识,以下面代码,先不看答案想一想会打印出什么blog
1 public class SuperClass { 2 public static int value = 123; 3 4 static { 5 System.out.println("super class init"); 6 } 7 } 8 9 public class SubClass extends SuperClass { 10 11 static { 12 System.out.println("Sub class init"); 13 } 14 } 15 16 public class ClassInit { 17 public static void main(String[] args) { 18 System.out.println(SubClass.value); 19 } 20 }
打印结果以下:生命周期
解析:当Main方法执行的时候,不会对SubClass类进行初始化,由于调用静态变量时只会初始化直接定义该变量的类,所以,上述代码只有SuperClass会被初始化。而SubClass并不会被初始化。
咱们稍微修改一个上述代码,将main方法放入子类中执行,执行main方法以后,代码会怎么执行呢?
1 public class SuperClass { 2 public static int value = 123; 3 4 static { 5 System.out.println("super class init"); 6 } 7 } 8 9 public class SubClass extends SuperClass { 10 11 static { 12 System.out.println("Sub class init"); 13 } 14 15 public static void main(String[] args) { 16 System.out.println(SubClass.value); 17 } 18 }
打印以下图:
解析:根据上述类初始化规定。根据第四条执行main方法时候必须初始当前类,所以触发了SubClass的初始化。根据第三条,若是要触发SubClass,必须先对SuperClass进行初始化。所以会先进行SuperClass的初始化、执行完成后执行SubClass初始化,最后等SubClass初始化完毕,打印出Main方法的中的语句。
1 public class StaticTest { 2 3 static int b = 200; 4 5 static StaticTest st = new StaticTest(); 6 7 static { 8 System.out.println("1"); 9 } 10 11 { 12 System.out.println("2"); 13 } 14 15 StaticTest() { 16 System.out.println("3"); 17 System.out.println("a=" + a + ",b=" + b+",c="+c+",d="+d); 18 } 19 20 public static void staticFunction() { 21 System.out.println("4"); 22 } 23 24 int a = 100; 25 26 static int c = 300; 27 28 static final int d=400; 29 30 public static void main(String[] args) { 31 staticFunction(); 32 } 33 }
执行结果以下:
分析:代码执行以后的结果跟我一开始预想的不大同样,咱们按照执行顺序进行分析。当咱们执行Main方法的以前,javac须要先将代码编译,在这个时候d属性已经完成了赋值。前面说过,在执行main方法以前,会对main方法所在的类进行初始化。根据属性是否静态,咱们大概能够将代码分为两部分:
一、静态代码
1 static int b = 200; 2 3 static StaticTest st = new StaticTest(); 4 5 static { 6 System.out.println("1"); 7 } 8 public static void staticFunction() { 9 System.out.println("4"); 10 }
二、非静态代码:
1 { 2 System.out.println("2"); 3 } 4 5 StaticTest() { 6 System.out.println("3"); 7 System.out.println("a=" + a + ",b=" + b + ",c=" + c + ",d=" + d); 8 } 9 10 int a = 100;
把代码分红两部分,主要是为了区分哪些是类初始化里的代码(<clinit>()中的代码,在类初始化的时候执行),哪些对象初始化代码(<init>()中的代码,对象初始化的时候执行)。main方法触发了类的初始化,所以会执行<clinit>()中的代码,执行顺序从上而下,先完成b=200赋值语句,紧接着执行 static StaticTest st = new StaticTest(),而对st的赋值则触发了对象初始化方法,所以会执行<init>()方法,即非静态代码,对象的初始化执行顺序和类初始化执行顺序不相同,类初始化执行顺序 属性初始化 =》代码块 =》方法初始化。所以在非静态代码中执行顺序为: 第10行=》第2行=》第6行=》第7行。因此最先打印出二、3。紧接着打印a、b、c、d数值的时候a、b、d已经完成赋值。完成对象初始化以后,继续执行上面的静态代码,打印出1。等类已经完成了加载,执行main方法,打印出4。
在java类加载器对Class进行加载的时候,若是两个类被不一样的类加载器加载,则这两个类不相等。经过equals(),instanceof等方法判断结果为false。关于Java的类加载器,大概能够划分为如下几种:
咱们的应用程序大部分是经过上面三种类加载器配合完成的,若是有特殊需求,还能够自定义本身的类加载器。包括类记载器在内,各类类加载器的关系能够这样表示。
上述关系图称为双亲委派模型,除了Bootstrap ClassLoad加载器,其余的类加载器都有本身的父类。当一个类加载器获取到一个类加载任务时,先将该类丢给父加载器加载, 若是父加载器不能加载则本身加载。这种加载方式就称之为双亲委派。如上图所示,自定义类加载器获取到一个加载任务,一层层往上丢,因此最早让启动类加载器加载,若是启动类加载器能加载,则启动类加载器加载,启动类加载器不能加载,则丢给扩展类加载器,若是扩展类加载器不能加载,则丢给应用类加载器,若是应用类加载器不能加载,才丢给自定义加载器加载。
上述的加载方式看起来特别麻烦,可是却解决了一个很重要的问题。好比自定义类加载器获取到一个Java.lang.Object的任务,则让Bootstrap ClassLoader加载,不然若是用户本身定义了一个Java.lang.Object会跟rt.jar中的类产生冲突,经过双亲委派模型,则用户本身写的Object将永远不会被加载到。
双亲委派模型是Java虚拟机推荐给开发者的类加载实现,并非一个强制性约束。在一些状况下双亲委派模型是会被破坏的,好比为了加载JNDI提供者的代码,设计出来的线程上下文加载器。又好比OSGI环境下规则也不大同样。