JAVA虚拟机加载机制
流程图
类的生命周期为类从被ClassLoader加载到虚拟机内存中开始,到卸载出内存为止的过程。
加载(Loading)
- 加载时机:虚拟机规范中没有强制约束,交给虚拟机实现。
- 类加载器:启动类加载器➔扩展类加载器➔系统类加载器➔用户自定义类加载器。
- 双亲委派原则+沙箱机制:一个特定的类加载器在接到加载某个类的请求时,首先将加载任务交给父加载器。父加载器能完成类的加载就成功返回,父加载器无法加载才由自己加载。
- 如何加载:
1、通过类的全限定名获取定义此类的二进制字节流
2、将字节流所代表的静态存储结构(静态常量池)转换为方法区的运行时数据结构(运行时常量池,java7之后字符串常量存放到堆的字符串常量池,其他内容还在方法区)
3、在java堆生成一个java.lang.Class对象,作为方法区数据的访问入口。
注意:一个类只会被加载一次。Class对象的
getClassLoader()方法获取加载该类的类加载器。Class.forName(全限定名)默认会使用调用类的类加载器来进行类加载。
验证(Verification)
1、验证文件格式
2、验证元数据
3、验证字节码
4、验证符号引用
准备(Preparation)
正式为类变量分配内存并设置类变量初始值的阶段。
1、类变量:static修饰,不包含常量(final static),常量在加载的时候分配到常量池中去了。
2、设置初始值:
public static int i; //初始值为0
public static int i = 123; //初始值为0
public static String value ; //初始为null
public static String value = “”; //初始为null
解析(Resolution)
虚拟机将常量池的符号引用转为直接引用,可能发生在初始化的后面。
1、符号引用:用一组符号描述引用的目标,引用的目标不一定被加载到内存中来了。
2、直接引用:直接指向目标的指针、相对偏移量或句柄。引用的目标一定在内存中存在。
初始化(Initialization)
在这个阶段,静态的(变量,方法,代码块)会被执行。变量和代码块的执行顺序按照代码的先后顺序执行。一个类只会初始化一次。
public class Test {
public final static int i = 1; //常量,存放在常量池
public static int j; //类变量,初始值为0(准备)
public static int k = 1; //类变量,初始值为0(准备),初始化值为1(初始化)
public static int m = k; //类变量,初始值为0(准备), 初始化值为1(初始化),不能使用定义在他后面的类变量
static { //静态代码块,按代码先后顺序执行
j = k; //初始化值为1(初始化),不能使用定义在他后面的类变量
}
public static int p = 1; //类变量,初始值为0(准备),初始化值为1(初始化)
/**
* 静态方法存储到方法区
* @return
*/
public static int getI() {
return i;
}
}
类初始化的时机(有且仅有以下几种情况,称为主动引入):
1、遇到new(实例化)、getstatic(获取类变量)、putstatic(设置类变量)或invokestatic(调用一个类的静态方法)这四条字节码指令时,如果类没有进行过初始化,则需要先对其进行初始化。(因为这些变量和方法是在初始化阶段被执行的)
2、 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。(使用之前要初始化)
3、 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
被动引用
1、通过子类引用父类的静态字段,不会导致子类初始化。
2、通过数组定义来引用类,不会触发此类的初始化。
3、引用常量
使用(Using)
主要包含类的实例化
1、通过new实例化类
2、通过反射实例化类(newInstance)
3、clone()
4、反序列化
类的实例的过程:实例属性(成员变量)➔实例代码块➔构造方法
1、实例属性、实例代码块在构造方法之前执行
2、实例变量默认值是在实例属性显示赋值和实例代码块之前执行的
2、实例属性显示赋值和实例代码块按先后顺序执行
public class Test {
public int i = 1;
public int j; //默认值为0
public int k = i;
public int m = getM();
private int getM() {
return n; //此时n还没有设置值,m=0
}
{ //实例代码块
n = 1; //定义在之后的变量可以赋值
p = 1;
// j = n; //但是不能使用
}
public int n = 2; //最终n的值为2
public int p; //最终p的值为1
{ //实例代码块
j = n; //可以使用定义在前面的变量
}
/**
* 构造器
*/
public Test() {
}
}
有父类的时候:父类初始化➔子类初始化➔父类实例化➔子类实例化
- 父类
public class SuperClass {
static {
System.out.println("SuperClass 静态代码块..."); //初始化,执行一次
}
{
System.out.println("SuperClass实例代码块");
}
public SuperClass() {
super();
System.out.println("SuperClass构造器");
}
}
- 子类
public class SubClass extends SuperClass{
static {
System.out.println("SubClass 静态代码块...");
}
{
System.out.println("SubClass实例代码块");
}
public SubClass() {
super();
System.out.println("SubClass构造器");
}
}
- 测试
public class InitializationTest {
public static void main(String[] args) {
SubClass subClass = new SubClass();
}
}
- 运行结果
SuperClass 静态代码块... //父类初始化
SubClass 静态代码块... //子类初始化
SuperClass实例代码块
SuperClass构造器 //父类实例化
SubClass实例代码块
SubClass构造器 //子类实例化
卸载(Unloading)
无用的类会被卸载,但是判断调条件非常苛刻。(方法区垃圾回收)
1、该类的所有实例都被回收,java堆中不存在该类的任何实例
2、加载该类的ClassLoader被回收
3、该类对应的Class对象没有被任何引用,无法通过反射访问该类的方法