1、定义
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
2、概述
1)在Java语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的;Java天生具有的动态扩展的特性就是依赖运行期动态加载和动态连接这个特点实现的。
2)类的生命周期:加载(Loading)->验证(Verification)->准备(Preparation)->解析(Resolution)->初始化(Initialization)->使用(Using)->卸载(Unloading)
3)进行初始化的时机:
I. 遇到new,getstatic,putstatic,或invokestatic这四条字节码指令时,若类为初始化,则先触发其初始化。
常见场景:使用new实例化对象时,读取或设置类变量(常量除外,因为编译期已经将结果放入常量池中),调用类方法
II. 使用java.lang.reflect包中的方法进行反射调用时(详见反射一章中的用法)
III. 初始化一个类时,发现其父类还未初始化,则先触发其父类初始化
IV. 虚拟机启动时,用户需要指定一个执行的主类(包含main()方法的类),先将该类初始化
VI. 使用java.lang.invoke.MethodHandle
4)被动引用
虚拟机规范中指出:“有且只有”上述5种场景的行为为类的主动引用。其他所有引用类的方式都不会初始化,成为被动调用
例I. 通过子类调用父类的静态字段,子类不会进行初始化
例II. 通过数组来定义引用类,类不会初始化。如
没有初始化SuperClass类,而是初始化了虚拟机生成的名为“[Lorg.SuperClass”的类,该类封装了数组中应有的属性和方法(如length属性和clone方法);注:越界检查并非封装在该类中,而是封装在数组访问的xaload、xastore字节码指令中。
SuperClass[] arr = new SuperClass[10];
例III. 调用常量
public class A
{
public static final intANUM = 21;
}
public class B
{
public B()
{
System.out.println(A.ANUM);
}
}在编译阶段通过常量传播优化,已将21存入到B的常量池中,以后B对常量A.ANUM的引用实际上都转化为了B类对自身常量池的引用
3、类加载的过程:加载、验证、准备、解析、初始化
1)加载:
I. 通过一个类的全限定名来获取定义此类的二进制字节流
II. 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
III. 将内存中生成的一个代表此类的java.lang.Class对象,作为方法区这个类的各种数据访问接口
2)验证:连接阶段的第一步,确保Class文件的字节流中包含的信息符合当期虚拟机的要求,并且不会危害虚拟机的安全
3)准备:正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
注意此时分配内存的仅包括类变量,不包括实例变量。
public static intANUM =
21; ANUM在准备阶段后的初始值为0
public static final intANUM =
21; 常量ANUM具有ConstantValue属性,故会被赋值为21
(注意类变量和常量的诸多区别)
4)解析:虚拟机将常量池内的符号引用替换为直接引用的过程
5)初始化:类初始化阶段是类加载过程的最后一步,即执行类构造器<clinit>()方法的过程
I. <clinit>()方法是有编译期自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生,编译期收集的顺序有语句在源文件中出现的顺序决定,静态语句块只能访问定义在static块之前的变量,定义在之后的变量,只可赋值,但不能访问。
public class A {
static {
ANUM = 100;// 编译通过
// System.out.println(ANUM); 非法向前引用
}
public static intANUM = 21;
}更多变量初始化情况见:
II. <clinit>方法对于类或接口都不是必需的,如果类中没有静态语句块,和对类变量的赋值操作,编译期可以不为类生成<clinit>()
III.<clinit>是线程安全的
6)类加载器:”通过一个类的全限定名来获取描述此类的二进制字节流“的动作防盗Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现该动作的代码成为”类加载器“;
I. 每一个类加载器,都拥有一个独立的类名称空间;
II. 双亲委派模型
1>绝大部分Java程序会用到以下三种类加载器:
启动类加载器(Bootstrap ClassLoader):由C++语言实现,是虚拟机自身的一部分,加载Java核心的API
扩展类加载器(Extension ClassLoader):加载Java扩展API,即/lib/ext中的类,该加载器可以被开发者直接使用。
应用类加载器(Application ClassLoader):加载用户类路径(ClassPath)上所指定的类库。
2>双亲委派模型(Parent Delegation Model):要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是以继承(Inheritance)的关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。
3>双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
本文介绍Java虚拟机如何加载类到内存中,包括类加载的基本步骤、初始化时机及类加载器的工作原理。
1663

被折叠的 条评论
为什么被折叠?



