JVM类加载机制分为五部分:加载、连接(验证,准备,解析)、初始化;
加载
加载是类加载过程的第一个阶段。在这个阶段JVM将字节码从各个位置(网络磁盘)转换成二进制字节流加载到内存中,接着会为这个类在JVM的方法区创建一个Class对象,是这个类各个数据的访问入口。
连接
注意:连接阶段和加载阶段是交叉进行的
验证:是为了保证class文件的字节流中包含的信息符合虚拟机的要求
准备:这个阶段是正式为类变量分配内存并设置类变量初始值,这些内存都在方法区分配。这个时候进行内存分配的仅仅是类变量(被static修饰的),不包括实例变量,实例变量是在对象初始化时随着对象分配到java堆中。
比如public static int value = 3;变量value在准备阶段过后值为0,而不是3,因为这个阶段尚未开始执行任何java方法,把value赋值为3是在程序编译之后,存放在类构造器中,所以把value赋值为3的动作是在初始化阶段才会执行的
比如public static final value = 3;被static和final修饰的变量称为ConstValue属性。编译时会为value赋值为3,即在准备阶段value的值就为3。
初始化:
这个阶段主要是进行类初始化,是执行类构造器的过程。换句话说,是只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类还没有初始化,则优先初始化父类,如果同时包含多个静态变量和静态代码块,按照自上而下的顺序依次执行。
<clinit()> 类构造器方法
编译器自动收集类中的所有类变量赋值动作和静态代码块,按在源文件中出现的顺序。注意:静态代码块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值不能访问,列如:
public class Test{
static {
i = 2; //编译可以通过
System.out.print(i); //编译器报"非法的前向引用"
}
static int i = 1;
}
与类的构造函数(实例构造器<init()>)不同,不需要显示的调用父类构造器,JVM会保证在子类的()构造器执行之前,父类的<clinit()>方法已经执行完毕,因此JVM第一个执行的<clinit()>肯定是java.lang.Object;
由于父类的<clinit()>先执行,意味着父类定义的静态语句块要优先与子类的赋值操作
类加载器的分类:
启动类加载器
扩展类加载器
系统类加载器
双亲委派机制
流程:如果一个类加载器收到类加载的请求,它首先不会自己去请求加载这个类,而是把请求委托给父加载器去完成,依次向上,因此所有的请求都会被传递到顶层的类加载器中,只有当父加载器在他的搜索范围内没有找到所需的类时,子类才会加载该类
优点:可避免重复加载,父类已经加载了,子类就不需要加载了;更加安全,很好的解决了各个类加载器的基础类的统一问题,如果不使用这种方式,用户自定义类加载器来随意加载核心api,会有隐患。