JVM-004-类加载机制

2018-07-10  本文已影响0人  井易安

虚拟机类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析、初始化,最终形成可以被虚拟机直接使用的Java类型。

在Java里面,类型的加载、连接和初始化过程都是在程序运行期间完成,不同于在编译时需要进行连接工作的语言。

java支持动态扩展就是依赖运行时动态加载和动态连接实现的

1. 什么时候进行类加载

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

image

除了解析之外其他步骤的顺序是确定的,解析在某些情况下可以在初始化阶段之后在开始,目的是为了支持JAVA语言的运行时绑定(动态绑定)

1.1 加载
并没有进行强制性约束,交给虚拟机根据具体实现在自有把握

1.2 初始化
严格规定了5种情况必须对类进行初始化,在这之前验证 准备 解析已经完成

1.2.1 一些被动引用的例子

关于接口与类不同的地方在:初始化一个类的时候要求其父类全部都已经初始化过了,但是一个接口在初始化的时候不要求其父接口全部完成初始化,只有在真正使用到父接口的时候例如引用了父接口中定义的常量才会被初始化。

2. 类加载的过程

2.1加载
加载主要完成三件事情

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

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中,然后再内存中实例化一个java.lang.Class类的对象 这个对象存储在方法区里而不是存放普通对象的堆中。

加载还未完成,连接阶段可能已经开始了,这两个阶段的开始时间仍然保持着固定的先后顺序。

类 和 数组加载过程的区别?

数组也有类型,称为“数组类型”。如:

String[] str = new String[10]; 

这个数组的数组类型是Ljava.lang.String,而String只是这个数组中元素的类型。

当程序在运行过程中遇到new关键字创建一个数组时,由JVM直接创建数组类,再由类加载器创建数组中的元素类。

而普通类的加载由类加载器完成。既可以使用系统提供的引导类加载器,也可以使用用户自定义的类加载器。

2.2验证
主要目的是确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全 包括以下验证

2.3准备

需要注意的是 类变量(被static修饰的变量)而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。

并且这里的初始值通常情况下是数据类型的零值,赋值操作是在编译后,初始化阶段才会执行。

2.4解析
解析就是虚拟机将常量池内的符号引用 替换为直接引用的过程

2.5初始化
初始化阶段就是执行类构造器clinit()的过程。
clinit()方法由编译器自动产生,收集类中static{}代码块中的类变量赋值语句和类中静态成员变量的赋值语句。

在准备阶段,类中静态成员变量已经完成了默认初始化,而在初始化阶段,clinit()方法对静态成员变量进行显示初始化。

初始化过程的注意点:

类加载器

把类加载过程的第一个阶段--加载阶段中的 通过一个类的全限定名来获取描述此类的二进制字节流 这个动作放到jvm外部去实现,以便让应用程序自己决定如何去获取所需要的类。

一个类需要由加载它的类加载器和这个类本身来确定它在java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。

比较两个类是否相等,只有在这两个类是否由同一个类加载器加载的前提下才有意义。

双亲委派模型

对于JVM来说类加载器分为两种

  1. 启动类加载器,使用C++实现,是虚拟机自身的一部分
  2. 其他的类加载器,Jva语言实现,独立于虚拟机外部,继承与抽象类 java.lang.ClassLoader

对于开发人员来说分为三种

  1. 启动类加载器,负责将放在lib目录中或者-Xbootclasspath参数指定的路径中的,并且是虚拟机识别的 类库 加载到虚拟机内存中。
  2. 扩展类加载器,负责将lib\ext目录中的,或者java.ext.dirs系统变量所指定的路径中的所有类库。
  3. 应用程序类加载器, 这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器。负责加载用户类路径(ClassPath)上所指定的类库
双亲委派模型

类加载器之间的这种层次关系被称为双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。类加载器之间的父子关系一般不会以继承的方式实现,而是都使用组合的关系来复用父加载器的代码。

双亲委派模型的工作过程

 protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            c = findBootstrapClassOrNull(name);
        }
        } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
    }
  1. 加载器比较依赖它的父类,收到类加载的请求时首先会去把这个请求委派给父类加载器去完成,所以所有的加载器请求最终都会传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(在它的搜索范围内没有找到这个类)时,自加载器才会尝试自己加载。
  2. 使用双亲委派模型,java类随着类加载器一起具备了一种带有优先级的层次关系。比如Object类只会在顶层的启动类加载器中加载,这样系统中就不会出现多个不同的Object类,如果执意要写一个重名的java类,那么这个类永远都不会被加载。
  3. 对于基础类又要调用回用户代码怎么解决?
    线程上下文类加载器,通过这个加载器,福类加载器可以请求自类加载器去完成类加载活动。这种行为违背了双亲委派模型的一般性原则。
上一篇下一篇

猜你喜欢

热点阅读