一.分类
类的生命周期分为加载,连接,初始化,使用,卸载五个阶段,其中连接分为验证,准备,解析
二. 具体细节
1.加载
定义:加载是类加载器通过不同的渠道(如本地文件,动态代理生成,网络传输的类)根据类的全限定名以二进制流的方式获取字节码信息。
在类加载阶段会方法区生成一个InstanceKlass对象来存储类的所有信息,同时还会在堆中也生成一个java.lang.Class对象来存储类的部分信息,并且在JDK8之后还会存储静态字段的数据。
作为开发者的我们,我们只能访问堆中的信息,不能访问方法区中的信息,有效的保护了程序。
2.连接
(1).验证
它会验证内容是否满足java虚拟机规范来确保不会危害虚拟机,如验证文件类型等。
(2).准备
它会为静态变量赋初值
a.如果程序中有
public static int a;
或
public static int a = 1;
JVM都会在堆中为它开辟一块空间,并赋上初值0,即时是第二个也是赋0。
b.如果变量是静态变量且被final修饰,且等号右边是常量,则虚拟机会直接赋上对应的值
如
public static final int a = 10;
JVM会在堆中开辟一块空间,赋上初值10。
c.如果变量是静态变量且被final修饰,但等号右边是变量,则虚拟机会直接赋上0
如
public static final int a = Integer.valueOf(1);
JVM都会在堆中为它开辟一块空间,并赋上初值0.
(3).解析
解析阶段将常量池中的符号引用替换直接引用
符号引用:
如上图,第一个图引用第二个图的内容,第二个图应用第三个图的内容。
直接引用:第一个图直接引用第三个图信息地址。
3.初始化
初始化阶段会执行静态代码块中的代码,并为静态代码块赋值。
初始化阶段会执行字节码文件中的clinit部分的字节码指令
(1).初始化时会按照开发者写的顺序执行静态代码块和给静态变量赋值的语句
如下两图
public class Main {
public static void main(String[] args) {
System.out.println(demo.a);
}
}
class demo {
public static int a = 1;
static {
a = 2;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(demo.a);
}
}
class demo {
static {
a = 2;
}
public static int a = 1;
}
第一个图输出是2,第二个是1。
在连接阶段已经为a开辟过空间,第一个图先执行将a的值由0变为1,然后再静态代码块,第二个相反。
(2).会初始化的几种情况
a.访问一个类的静态方法或静态变量(变量不能被同时final修饰,右边为常量)。
b.调用Class.forName();
c.调用构造方法
d.执行main方法
例:
public class Main {
public static void main(String[] args) {
System.out.println("D");
new Main();
new Main();
}
public Main() {
System.out.println("B");
}
{
System.out.println("A");
}
static {
System.out.println("C");
}
首先执行Main方法之前会执行静态代码块输出C,然后再输出D,接着new Main(),因为执行main方法已经初始化过Main类了,所以不会再次初始化,但是执行构造方法之前会优先执行普通代码块,所以输出AB,所以结果是CDABAB。
(3).不会初始化的几种情况(初始化无作用时会直接跳过)
a.无静态代码块,无静态变量赋值语句
b.由静态变量,但是没有赋值语句
public static int a;
c.静态变量被final修饰,且右边是变量
public static final int a = 1;
(4)继承之下的初始化
a.直接访问父类的静态变量,不会触发子类的初始化。
b.子类初始化之前会先初始化父类
例:
public class Main {
public static void main(String[] args) {
System.out.println(Son.a);
}
}
class Son extends father {
static {
a = 2;
}
}
class father {
public static int a = 0;
static {
a = 1;
}
}
因为直接调用的父类中的静态变量,虽然是通过子类调用的,但子类依然不会初始化,所以输出1。
例:
public class Main {
public static void main(String[] args) {
new Son();
System.out.println(father.a);
}
}
class Son extends father {
static {
a = 2;
}
}
class father {
public static int a = 0;
static {
a = 1;
}
}
因为初始化Son之前会初始化father所以会先将a赋为1,再经过Son初始化赋为2,虽然调用了father中的a变量,但是a已经被赋值为2,所以输出2.
4.其他
1.使用就是日常使用。
2.卸载单独发文章。