01.JVM-类加载

类的加载流程

image

类的加载流程包含:加载、验证、准备、解析、初始化java

加载

做为第一个阶段,虚拟机须要完成了之后三个步骤缓存

  1. 经过一个类的全限类名来获取定义的二进制字节流
  2. 将这个字节流所存储的静态结构转化为方法区运行时候的数据结构
  3. 在java堆中,生成一个表明这个类的java.lang.Class对象,最为对方法区中这些数据的访问入口

验证

确保被加载类的正确性,包含检测如下信息安全

  1. 文件格式的验证:验证字节流是否符合Class文件格式的规范
  2. 元数据的验证::对字节码描述的信息进行语义分析
  3. 字节码的验证:经过数据流和控制流分析,肯定程序语义是合法的、符合逻辑的。
  4. 符号引用验证:确保解析动做能正确执行。

准备

为类的静态变量分配内存,并将其初始化默认值bash

  • 这个时候进行内存分配的仅是类变量(static),而不包括实例变量,实例变量会在对象实例化的时候,随着对象一块分配到堆中
  • 这的初始化默认值,一般状况下是数据类型默认的零值(如0、0L、null、false等),而不是指的是显示赋值

解析

将类中的符号引用转换为直接引用网络

解析阶段就是虚拟机将常量池内符号引用替换为直接引用,解析的动做主要是针对类的接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。数据结构

  • 符号引用与虚拟机实现的布局无关,引用的目标并不必定要已经加载到内存中。各类虚拟机实现的内存布局能够各不相同,可是它们能接受的符号引用必须是一致的,由于符号引用的字面量形式明肯定义在Java虚拟机规范的Class文件格式中。
  • 直接引用能够是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。若是有了直接引用,那引用的目标一定已经在内存中存在。

初始化

为类的静态变量赋予正确的初始值,到了初始阶段,才开始真正执行类中定义的Java程序代码。jvm

  • 声明类变量是指定初始值
  • 使用静态代码块为类变量指定初始值

类的加载器

示例图布局

image

==注意:这里父类加载器并非经过继承关系来实现的,而是采用组合实现的==加密

在java虚拟机的角度来讲只存在两种不一样的类加载器spa

  • 启动类的加载器:使用C++实现
  • 全部其余类的加载器:使用java语言实现的,独立于虚拟机以外,而且所有继承自抽象类java.lang.Classlocad,这些类的加载器须要由启动类加载器加载到内存中以后才能去加载其余类

在开发者的角度来讲,类的加载器能够大体划分为三类

  • 启动类加载器
  • 扩展类加载器
  • 应用程序类加载器

类加载器的加载机制

  • 全盘负责:当一个类加载器负责加载某Class时候,该Class依赖的Class和引用的Classs也将由该类加载器负责载入,除非显示的使用另外一个类加载器来载入
  • 父类委托:先让父类加载器试图加载该类,只有在父类加载器没法加载该类的时候,才尝试从本身的类路径中加载该类
  • 缓存机制:缓存机制将保证全部加载过的Class都会被缓存,当程序中须要使用某一个Class,类加载器会如今缓存中查找该Class,只要缓存不存在的时候,系统才会读取该类对应的二进制数据,将其转化成Class对象,存入缓存区。这就是为何修改了Class后,必须重启JVM,程序的修改才会生效

类的加载

类加载的方式

  • 命令行启动应用时候由JVM初始化加载
  • 经过Class.forName()方法动态加载:将类的.class文件加载到jvm中,并会对类进行解释,执行类的static块
  • 经过ClassLoader.loadClass()方法动态加载:只作一件事,将类的.class文件加载到jvm中,不会执行static块,只有在newInstance才会去执行static块

双亲委派机制

双亲委派机制的流程:若是一个类加载器收到了一个类加载的请求,首先他不会尝试本身加载该类,而是把这个请求委托给父类加载器加载该类,依次向上传递,所以,全部的类记载请求都会请求到启动类加载器中,父类加载器在他的搜索范围内没有找到所须要的类时,即没法完成该类的加载,子加载器才会尝试本身去加载该类,若是尚未找到,那么直接抛出异常

示例流程详解

    1. AppClassLoader加载一个class时,它首先不会本身去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
    1. ExtClassLoader加载一个class时,它首先也不会本身去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
    1. 若是 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加该类
    1. ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,若是 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException。

双亲委派机制的意义

  • 系统类防止内存中出现多份一样的字节码
  • 保证Java程序安全稳定运行

代码示例

// 首先判断该类型是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                //若是没有被加载,就委托给父类加载或者委派给启动类加载器加载
                    if (parent != null) {
                     
            //若是存在父类加载器,就委派给父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                    //若是不存在父类加载器,就检查是不是由启动类加载器加载的类,经过调用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
复制代码

自定义的类加载器

一般状况下,咱们都是直接使用系统类加载器。可是,有的时候,咱们也须要自定义类加载器。好比应用是经过网络来传输 Java类的字节码,为保证安全性,这些字节码通过了加密处理,这时系统类加载器就没法对其进行加载,这样则须要自定义类加载器来实现。自定义类加载器通常都是继承自 ClassLoader类,从上面对 loadClass方法来分析来看,咱们只须要重写 findClass 方法便可。

自定义类加载器的核心在于对字节码文件的获取,若是是加密的字节码则须要在该类中对文件进行解密