Java虚拟机——类加载机制

一、类加载概述

我们平时所写的java代码,基本都是在一个类/接口中,存储在一个.java类型的文件中。这个文件在使用时首先会被javac编译器编译为.class文件,.class文件时一组8位字节为基础的二级制流。java虚拟机把描述类数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。


扩充一个问题:刚开始学习java的时候也是粗浅的认为java虚拟机只是为java提供服务的,但随着学习的深入,了解到如今还有别的语言也可以java虚拟机上运行,如Jython、JRuby、Scala等。这些语言只需要用自己专属的编译器编译为.class文件,也就可以在java虚拟机上使用。

 

二、类的生命周期

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:

加载、验证、准备、解析、初始化、使用、卸载

其中验证、准备、解析三个阶段统称为连接。

三、类的加载的过程

类的加载的全过程,也就是加载、验证、准备、解析和初始化这五个过程。

1、加载

这里的加载不是类加载,要区分开这两个概念。

在加载阶段,虚拟机需要完成以下三件事:

  • 通过一个类的全限定名称来获取定义此类的二进制字节流(获取方式规则内不限)
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

2、验证

主要是验证Class文件中的字节流 包含的信息是否符合虚拟机的要求,并且不会危害虚拟的自身的安全。

包括:

  • 文件格式验证:验证字节流是否符合Class文件格式的规范
  • 元数据验证:对字节码描述的语义进行分析,以保证其符合规范
  • 字节码验证:通过数据流和控制流分析程序语义是合法的符合逻辑的

3、准备

准备阶段是正式为类变量(仅包括被static修饰的变量)分配内存并设置类变量初始值的阶段,这些变量使用的初始值都在方法区进行分配。

这里的类变量仅为静态变量,实例变量会随着初始化在java堆中分配内存。而且这里的初始值都为零值,比如:

public static int value=233;

这里的初始值不是233而是0,将vlaue赋值为123在初始化阶段才会进行。

4、解析

解析阶段是虚拟机将常量池内的符号引用转为直接引用的过程。

符号引用:用一串字符来描述所引用的目标

直接引用:直接引用可以是直接指向目标的指针、相对偏移量或者句柄。

解析的目标包括:类/接口、字段、类方法、接口方法

5、初始化

初始化时类加载过程的最后一步,我们可以将初始化阶段看做是执行类构造器<clint>() 方法的过程。

  • <clint>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生,收集顺序由源文件中的出现顺序决定,静态代码块只能对定义在它之前的变量进行访问,之后定义的变量只能赋值但是不能访问。

  • <clint>()方法和构造函数不同,他不需要显示的调用父类构造器,虚拟机会保证<clint>()方法执行前它父类的<clint>()方法已经执行,所以虚拟机中第一个执行的<clint>()方法的类肯定是Object。父类中的<clint>()方法优先执行也就意味着有操作优先权。

  • <clint>()对于接口或类来说不是必须的,如果一个类中没有静态代码块和赋值动作,那么就不会生成<clint>()。

  • 接口中不会有静态代码块,但是同样会有赋值动作,所以也会生成<clint>(),但是执行的时候不需要执行父类的<clint>()。

  • 虚拟机会保证一个类的<clint>()在多线程环境下能被正确的加锁、同步,如果一个线程去初始化一个类,那么别的线程就会处于阻塞状态,直到第一个线程执行<clint>()方法完毕。

四、类加载器

在类加载过程中的加载阶段:有这样一个操作:根据类的全限定名称去获取这个类的二进制字节流。实际上这个过程在虚拟机的外部实行。实现这个动作的代码模块称为“类加载器”。

类加载器的作用不仅仅是实现类的加载动作,对于任意一个类,都需要加载它的加载器和这个类本身一同确立其在java虚拟机中的唯一性。来源于同一个class文件的两个加载的虚拟机不同,那么他们就必然不同。

 

猜你喜欢

转载自blog.csdn.net/qq_40692753/article/details/83445116