JAVA虚拟机加载机制

JAVA虚拟机加载机制

流程图

在这里插入图片描述

类的生命周期为类从被ClassLoader加载到虚拟机内存中开始,到卸载出内存为止的过程。

加载(Loading)

  1. 加载时机:虚拟机规范中没有强制约束,交给虚拟机实现。
  2. 类加载器:启动类加载器➔扩展类加载器➔系统类加载器➔用户自定义类加载器。
  3. 双亲委派原则+沙箱机制:一个特定的类加载器在接到加载某个类的请求时,首先将加载任务交给父加载器。父加载器能完成类的加载就成功返回,父加载器无法加载才由自己加载。
  4. 如何加载:

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对象没有被任何引用,无法通过反射访问该类的方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值