类的加载过程详解

在java中数据类型分为基本数据类型和引用数据类型。基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载。

按照java虚拟机规范,从class文件到加载进入内存中的类,再到类卸载出内存为止,整个生命周期如下

一、加载

将java类的字节码文件加载到机器内存中,并在内存中构建出java类的原型(类模板对象)

1、加载类时,java虚拟机加载步骤

(1)通过类的全名,获取类的二进制数据流

(2)解析类的二进制数据流为方法区内的数据结构(Java类模型)

(3)创建java.lang.Class类的实例,作为方法区这个类的各种数据的访问入口

2、类模型和Class实例的位置

(1)类模型存储在方法区

(2)class文件加载到元空间后,会在堆中创建一个Class对象,用来封装类位于方法区内的数据结构。每一个类都对应一个Class对象

Class类的构造方法是私有的,只有jvm可以创建。

3、数组类的加载

数组类本身并不是由类加载器负责创建,而是由jvm在运行时根据需要直接创建的,但是数组的元素类型仍然需要依靠类加载器去创建。创建步骤如下

(1)如果数组的元素类型是引用类型,那么遵循定义的加载过程递归加载和创建数组A的元素类型

(2)jvm使用指定的元素类型和数组维度类创建新的数组类。

二、链接

1、验证

保证加载的字节码是合法的。

验证通过之后类加载器才会将类的二进制数据加载到方法区中,除格式验证之外的验证操作是在方法区中进行

(1)格式验证

是否以魔数0xCAFEBABE开头,主版本和副版本是否在当前的java虚拟机的支持范围内等。

(2)语义检查

是否所有的类都存在父类(在java里,除了Object之外,其他类都有父类)

是否一些被定义为final的方法或者类被重写或者继承了

非抽象类是否实现了所有抽象方法或者接口方法

是否存在不兼容的方法

(3)字节码验证

在字节码执行的过程中,是否会跳转到一条不存在的指令(不能达到100%验证)

函数的调用是否传递了正确类型的参数

变量的赋值是不是给了正确的数据类型等

注:

栈映射帧(StackMapTable)是处于该阶段,在检测特定的字节码时,其局部变量表和操作数栈是否有着正确的数据类型。

(4)符号引用验证

Class文件在常量池会通过字符串记录自己将要使用的其他类或者方法。在验证阶段,虚拟机会检查类或者方法是否存在,访问权限是否受限等。如果一个类在系统中找不到,则抛出NoClassDefFoundError,如果一个方法无法找到,则抛出NoSuchMethodError。

2、准备

当一个类通过验证,就会进入准备阶段,为静态变量分配内存,并初始化默认值。

java不支持boolean类型,内部实现是int,由于int默认值为0,所以boolean的默认值为false.

3、解析

将接口,字段和方法的符号引用转换为直接引用。

java虚拟机为每一个类都准备一张方法表,将其所有的方法列在其中,当需要调用一个类的方法时,只要知道这个方法在方法表的偏移量(通过解析操作,符号引用转换为直接引用类中的方法表中的位置),就可以直接调用该方法了。

不过java虚拟机规范并没有明确要求解析阶段一定要按序执行,在hotspot中,按照加载,验证,准备和初始化的顺序执行,但链接阶段的解析操作往往伴随着jvm执行完初始化之后再执行。

三、初始化

类的初始化时类装载的最后一个阶段,也是真正开始执行类定义的java的程序代码<clinit>()方法

在加载一个类之前会先加载其父类,因此父类的<clinit>方法总是在子类<clinit>方法之前被调用。

1、使用static+final修饰,且显示赋值中不涉及到方法或者构造器调用的基本数据类型或者String类型的显示赋值,是在链接阶段的准备环节进行赋值。

public static int a = 1;//在初始化阶段<clinit>()中赋值
public static final int INT_CONSTANT = 10;//在链接阶段的准备环节赋值

public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100);//在初始化阶段<clinit>()中赋值
public static Integer INTEGER_CONSTANT2 = Integer.valueOf(1000);//在初始化阶段<clinit>()中赋值

public static final String s0 = "helloworld0";//在链接阶段的准备环节赋值
public static final String s1 = new String("helloworld1");//在初始化阶段<clinit>()中赋值

public static String s2 = "helloworld2";

public static final int NUM1 = new Random().nextInt(10);//在初始化阶段<clinit>()中赋值

2、<clinit>线程安全性

虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁,同步。

class StaticA {
    static {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        try {
            Class.forName("com.atguigu.java1.StaticB");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("StaticA init OK");
    }
}
class StaticB {
    static {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        try {
            Class.forName("com.atguigu.java1.StaticA");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("StaticB init OK");
    }
}

public class StaticDeadLockMain extends Thread {
    private char flag;

    public StaticDeadLockMain(char flag) {
        this.flag = flag;
        this.setName("Thread" + flag);
    }

    @Override
    public void run() {
        try {
            Class.forName("com.atguigu.java1.Static" + flag);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " over");
    }

    public static void main(String[] args) throws InterruptedException {
        StaticDeadLockMain loadA = new StaticDeadLockMain('A');
        loadA.start();
        StaticDeadLockMain loadB = new StaticDeadLockMain('B');
        loadB.start();
    }
}

  • 8
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值