虚拟机类加载机制


虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
虚拟机的类加载机制是在运行时期完成的,这种策略会增加一些运行时的性能开销,但是会给java程序提供高的灵活性,java动态扩展的语言特性就是通过运行时动态加载和动态链接实现的。

类加载的过程

类从被加载到虚拟机开始到卸载出内存为止,生命周期一共包含以下几个阶段:
加载、验证、准备、解析、初始化、使用、卸载
其中验证、准备、解析、初始化、称为连接阶段

  1. 加载
    类加载阶段虚拟机需要完成以下三件事情
    a. 通过类的全限定名来获取此类的二进制字节流
    b. 将字节流代表的静态存储结构转化为方法区的运行时数据结果
    c. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
  2. 验证
    验证阶段是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
    验证阶段大致会完成下面4个阶段的检验动作:
    a. 文件格式校验 b.元数据验证 c字节码验证 d.符号引用验证
  3. 准备
    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
    注意:这里初始化的是类(static)变量,而不是实例变量。实例变量是在对象实例化时在堆中分配的。这里的初始值为变量的默认值如int会初始化为0,boolean会初始化为false
  4. 解析
    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
  5. 初始化
    到了初始化阶段,才真正执行类中定义的java程序代码,根据程序员的意愿对类变量进行初始化,如static int a =123;在准备阶段a的值为0,在初始化后a的值为123.
    其中解析阶段可以在初始化阶段的之前或之后执行。其他阶段必须严格按照顺序执行。

类加载时机

虚拟机规范严格规定,以下情况必须对类进行初始化:

  1. 遇到new、getstatic、putstatic、invokestatic时必须对类进行初始化
  2. 使用java.lang.reflect包的方法对类进行反射的时候,必须初始化
  3. 初始化一个类的时候,如果父类没有初始化,则需要先初始化父类
  4. 当虚拟机启动时,main函数所在的类必须初始化
  5. 如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

类加载器

虚拟机设计团队把类加载阶段中,“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到虚拟机的外部实现,以便应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

类与类加载器

对于任何一个类,都需要加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每个类加载器都拥有一个独立的命名空间。
这里的相等,包括代表类的Class对象的equals方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括instanceof对象所属关系的判断。

双亲委派模型

从java虚拟机的角度来讲,只存在两种不同的加载器:
1.一种是启动类加载器(Bootstrap ClassLoader)这个类加载器使用C++语言实现,是虚拟机的一部分
2.另一种是其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部。并且全部继承自抽象类java.lang.ClassLoader.

从java开发人员的角度看,还可以分的更细一些:

  1. 启动类加载器(Bootstrap ClassLoader):这个负责将存放在JAVAHOME/lib目录中,或者被Xbootclasspath参数指定的路劲中,可以被虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委托给引导类加载器,直接使用null代替即可。
  2. 扩展类加载器(Extension ClassLoader)这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JAVA_HOME/lib/ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器
  3. 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器时ClassLoader中的getSystemClassLoader方法的返回值,所以一般称为系统类加载器,它负责加载用户类路径(classpath)上所指定的类库,开发者可以直接使用这个类加载器。
  4. 如果有必要开发人员可以定义自己的类加载器。

这些加载器的关系如下图所示
java类加载器模型
双亲委派模型工作过程:
如果一个类加载器收到类的加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

破坏双亲委派模型

双亲委派模型并不是一个强制性的约束模型,而是java设计者推荐给开发者的类加载器实现方式,java中大部分的类加载器遵循这个模型,但是也有例外,例如OSGI中,类加载器不是双亲模式中的树形结构,而是复杂的网状结构。
OSGI在java中最著名的应用案例就是Eclipse IDE。
OSGI中每个模块与普通的Java类库区别并不太大,两者一般都以JAR格式进行封装,并且内部存储的都是Java Package和class,但是一个Boundle 可以声明它所依赖的JavaPackage,也可以声明它所导出的package,供外界访问,其他的package和class将会隐藏起来。除了精确的模块划分和可见性控制外,引入OSGI的另外一个重要理由是,基于OSGI的程序可以实现模块级的热插拔功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值