JVM之详解类加载的过程

​ 首先我们都知道,当我们编写完一个java类的时候,如果想要执行这个java类,首先需要通过javac命令将它便以为class的二进制文件,然后再通过java命令执行得到结果,那么当jvm将class文件加载到内存的这段期间都执行了哪些步骤呢,下面我就详细的介绍一下

1.加载

​ 这个阶段也就是类加载的第一个步骤,这里会通过类的全限定名来获得类的二进制流文件,然后会按照方法区中的数据格式将期存储在方法区中(jdk8中会存放在元数据区),之后会在方法区中实例化一个java.lang.Class对象,这个对象提供程序访问方法区的对外的接口,这个Class对象相对于普通的对象比较特殊,他并不是存放在堆内存中的,还要提一下的是,加载过程和下面的验证,准备等过程并不是串行执行的,当加载开始但还未结束时,下面的步骤就已经开始执行了

2.验证

​ 这一阶段主要是来校验class文件是否符合jvm的规范,确保jvm的安全性,该阶段主要会对魔术,元数据等等进行校验,主要就是确保class文件的规范性和安全性

3.准备

​ 这一个阶段会对类中的类变量(static修饰的变量)进行初始化的赋值操作,并在方法区中分配内存,要说明的是,该阶段只是会对类变量赋予jvm规定的默认的值,还不会赋予用户定义的变量值,例如 private static int a=666,在这一阶段会将这个值赋予int的初始值,也就是0,这个值会在执行putstatic指令之后被存入< clinit>中,并在类的初始化阶段进行赋值为666,还要注意的一点是,如果是被final修饰的类变量,在这里会直接赋值为用户定义的值,例如 private static final int b=123,这里就会不将b赋值为0,而是123.

4.解析

​ 该过程涉及到很多复杂的步骤,但是简单来说,解析阶段要做的事就是将类中的符号引用转换为直接引用,例如private static String name=“lex”,这里面的name就是一个符号引用,而这一阶段要做的就是将name转换为一个实际地址的一个指针,这样就可以通过这个name找到对应地址的常量池中的“lex”了

5.初始化

​ 该阶段会对之前在准备阶段初始化的静态变量进行再一次的赋值,这次会将用户定义的值赋值给相应的变量,这里编译器会自动收集该类中所有的静态代码块,以及对类变量的赋值操作,将他们组合成一个< clinit>方法,并执行,关于clinit方法有几点需要注意的地方。

  • 这个方法并不是一定会生成的,如果类中没有静态变量的赋值操作和静态代码块的话,编译器,不会生成clinit方法,反之,只要是静态代码块和静态变量赋值操作,二者有其中一个,都会生成clinit方法
  • 虚拟机会保证子类clinit方法执行前,父类的clinit方法已经执行完毕
  • 因为static代码块是在clinit中执行的,而父类的clinit还要在子类之前执行,也就是表明了父类的静态代码块要先与子类执行
  • 在接口中并没有static代码块,但是还是可以有静态变量的赋值,所以接口也可以有clinit方法
  • 虚拟机保证了clinit执行的原子性,如果有多个线程同时去初始化一个类,那么只有一个线程会执行到clinit方法,其他的线程会被阻塞,但如果执行clinit的线程结束完毕后,其他的线程也不能再次这行这个方法了,因为在同一个类加载器下,一个类型只会被初始化一次

6.演示一个类加载的全过程

  • 首先写一段测试代码
public class Demo {
	private static String name="lex";
	private static String gender;
	private static final num=123;
	private Integer age=18;
	static{
		gender="man";
	}
	
	public static void main(String[] args) {
		new Demo();
	}
		
}

  • 加载该类到方法区中,生成对应的class对象在方法区
  • 进行相关的验证
  • 准备阶段,会对静态变量进行初始化赋值,name=null,gender=null,num=123,因为num是final类型,所以会赋值为123,而age并不是静态变量,所以不做操作
  • 验证阶段会将符号引用转换为直接引用
  • 初始化阶段,会将静态代码块和静态变量赋值操作组合为一个clinit方法并执行,首先会执行private static String name=“lex”;的赋值操作,此时name=“lex”,然后继续执行静态代码块中的赋值,gender=“man”
  • 此时类加载的阶段完成,开始执行main方法,main方法会进入方法栈
  • 执行new对象的操作,这是会在堆内存开辟一块空间给这个对象,然后会在这块堆内存中将age的值初始化为0
  • 然后再将age的值赋值为18
  • 然后执行init方法,也就是构造方法

7.由此引出的静态和非静态访问的问题

​ 我们在写代码的时候经常会提到静态不能访问非静态,那么到底是为什么呢,结合上面这些就很好说明了,静态修饰的变量或方法是属于类的,这些东西是储存在方法区中的,通过类名直接访问,并不会指向堆内存,而非静态的实例变量都是存储在堆内存中的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值