类加载与初始化内存分析

1. 类加载步骤

类加载到内存中一个完整的生命周期总共有五大步骤(七小步骤):加载、链接、初始化、使用、卸载。其中链接部分又分为三小步骤(验证、准备、解析)。下面主要分析加载、链接与初始化。

在这里插入图片描述

  1. 加载:将.class字节码文件内容加载到内存中,并且将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象。

  2. 链接:将Java类的二进制代码何必管道JVM的运行状态之中的过程。

    • 验证: 确保加载的类信息符合JVM规范,没有安全方面的问题。

    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段。在方法区中分配(方法区是特殊的堆

    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用地址的过程。

  3. 初始化:

    • 执行类构造器< clinit >()方法的过程。类构造器< clinit >()方法是由编译器自动收集类中所有类遍历的赋值动作和静态代码块中的语句合并产生的。(注意这里不是实例化类的构造方法)

    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,会先将父类初始化。

    • 当初始化一个接口的时候,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候才会初始化。

    • 虚拟机会保证一个类的< clinit >()方法在多线程环境中会正确加锁和同步。
      在这里插入图片描述



2. 测试类加载

编写一个类A,Demo02主类。

class A{
    static {
        System.out.println("A的静态代码块初始化");
        //System.out.println(m);          //非法前向引用
        m = 300;
    }
    static int m = 100;
    public A(){
        System.out.println("A的构造方法初始化");
    }
}
public class Demo02 {
	static {
        System.out.println("Demo02主类初始化");
    }
    public static void main(String[] args) {
    
        System.out.println("----------------");
        A a = new A();
        System.out.println("----------------");
        System.out.println(a.m);
    }
}

在这里插入图片描述

整个过程可以分为三个步:

  • 加载到内存,产生一个Class对象

  • 完成常量池的引用、静态变量和方法的初始化。

  • 初始化

    < clinit >(){
    	//链接的准备阶段时m = 0的。
        System.out.println("A的静态代码块初始化");
        m = 300;
        m = 100;            //最终值
    }
    

注意:所有的static变量,静态代码块最后合并到clinit类构造器时会按照顺序依次的添加语句,没有先后之分;也就意味着如果将静态变量static int m = 100;写在静态代码块上面结果将会是300,因为没有先后顺序之分

static int m = 100;
static {
   System.out.println("A的静态代码块初始化");
    //System.out.println(m);          //非法前向引用
    m = 300;
}


<clinit>(){
	
	m = 100;
	System.out.println("A的静态代码块初始化");
	m = 300;			//最终值
}


3. 类的初始化(主动引用和被动引用)

package JVM.SevenClassLoader;

import org.junit.Test;

class Father{

    static {
        System.out.println("父类初始化");
    }
    static int fathervalue = 1;

}

class Son extends Father{
    static {
        System.out.println("子类初始化");
    }

    static int sonvalue = 2;
    static final int constvalue = 100;
}
public class Demo03 {				//main类
    static {
        System.out.println("Main初始化");
    }
}

主动引用(大前提:类型都没有进行过初始化)
  1. main方法启动,首先初始化main所在的类

  2. new关键词实例化对象。

  3. 调用静态方法、静态变量。

  4. 通过reflection进行反射调用

  5. 父类还未进行初始化

  6. 接口中定义了JDK8新特性(被default关键词修饰的接口方法),如果接口的实现类发生了初始化,那该接口要在其之前被初始化。

上面所有的主动引用前提是都没有进行过初始化,第一次new A()会进行初始化,第二次new就不会再初始化了。

@Test
public void test1() {
    // 1. new   主动引用
    //Son son = new Son();

    // 2. 反射   主动引用
    //Class.forName("JVM.base.Son");

    //3. 调用子类的静态变量
    System.out.println(Son.sonvalue);         

}

在这里插入图片描述


被动引用
  1. 当访问一个静态域时,只有真正声名这个域的类才会被初始化。例如:子类引用父类的静态变量,不会导致子类初始化但是父类会初始化。

  2. 通过数组定义类引用,即给数组分配空间时并不会触发类的初始化

  3. 引用常量,此时常量池在准备阶段已经完成了对引用地址的替换。同样也不会导致初始化。

 @Test
    public void test2(){
        // 被动引用
        System.out.println(Son.constvalue);             //调用常量
        System.out.println("----------------------");
        Son[] sons = new Son[5];                        //创建子类的数组,只是分配空间并没有进行实例化。
        System.out.println("----------------------");
        System.out.println(Son.fathervalue);            //调用父类的静态变量,引起父类进行初始化,子类不会
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值