JVM中的类加载器子系统

总览

在这里插入图片描述

  • 类加载器负责从文件或网络中加载class文件,在class文件的开头必须有特定的标识才可以被识别
  • 类加载器只负责class文件的加载,至于它是否可以运行,取决于ExecutionEngine决定
  • Class File 加载到JVM中被称为DNA元数据模板,存放在方法区。方法区除了存放类的基本信息外,还可以存放运行时常量池信息,可能还包括字符串字面量和数据常量(这部分常量信息是class文件中常量池部分的内存映射)

类加载阶段概述

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

加载.class文件的方式

  1. 从本地磁盘中直接加载
  2. 通过网络获取,典型场景: web 应用
  3. 从zip压缩包中读取,成为日后jar、war格式的基础
  4. 运行时计算生成,使用最多的是:动态代理技术
  5. 由其他文件生成,典型场景:JSP应用
  6. 从专有数据库中提取.class文件,比较少见
  7. 从加密文件中获取,典型的防Class文件被反编译的保护措施,如安卓应用

类加载器的分类

  • JVM支持两种类型的类加载器,分别为引导类加载器(BootstrapClassLoader)和自定义类加载器(User-Defined classLoader)。
  • 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类classLoader的类加载器都划分为自定义类加载器。
  • 无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示:

在这里插入图片描述

  • 引导类加载器(Bootstrap ClassLoader)

    • 这个类加载使用c/C++语言实现的,嵌套在JVM内部。
    • 它用来加载Java的核心库,只加载包名为java、 javax、sun等开头的类),用于提供JVM自身需要的类
    • 并不继承自java.lang.classLoader,没有父加载器
    • 加载扩展类和系统加戟器,并指定为他们的父类加载器。
  • 扩展类加载器(Ext ClassLoader):负责加载JAVA_HOME/lib/ext目录下的类库

  • 我们自己定义的类是使用:<系统类加载器>进行加载的

  • Java的核心类库都是使用:<引导类加载器>进行加载的

    public static void main(String[] args) {
//        获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);// sun.misc.Launcher$AppClassLoader@18b4aac2

//        获取其上层:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);// sun.misc.Launcher$ExtClassLoader@1b6d3586

//        获取其上层:引导类加载器 --> 获取不到
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);// null

        /**
         * 验证类是使用什么加载器
         */
//        我们自己定义的类是使用:<系统类加载器>进行加载的
        ClassLoader myClass = ClassLoaderType.class.getClassLoader();
        System.out.println(myClass);// sun.misc.Launcher$AppClassLoader@18b4aac2

//        Java的核心类库都是使用:<引导类加载器>进行加载的
        ClassLoader sysClass = String.class.getClassLoader();
        System.out.println(sysClass);// null

    }

双亲委派机制

Java虚拟机在加载class文件时是采用按需加载的方式进行加载的,而加载这个class文件时,就是采用的双亲委派机制,其流程如下:

  1. 当一个类加载器收到加载类的请求时,它并不是直接去加载这个类,而是把这个请求委托给父类加载器去执行,如果存在父类加载器还存在父类加载器,则进一步向上委托,直到最顶层的引导类加载器。
  2. 如果父类加载器可以完成该类的加载,就只用该加载器进行加载,若父类加载器不能进行加载这个类,就把加载这个类交给子加载器去加载,这就是双亲委派机制。

如我们自己新建一个java,lang包,在该包下新建一个String类,当我们只用这个String类的时候,系统类加载器是不会去加载我们编写的这个类的,因为这个java.lang.String这个类被引导类加载器进行了加载。

优点:

  1. 可以避免类被重复加载
  2. 可以保护程序的安全,防止核心API被随机篡改。如我们新建一个java.lang包,并在这个包下定义自己的类,引导类加载器是不会加载我们自定义的类的,会抛出一个安全异常。其实这就是沙箱安全机制的体现

在这里插入图片描述

用户自定义类加载器

在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。
为什么要自定义类加载器?

  • 隔离加载类
  • 修改类加载的方式
  • 扩展加载源
  • 防止源码泄漏

用户自定义类加载器实现步骤:

  1. 开发人员可以通过继承抽象类java.lang.classLoader类的方式,实现自己的类加载器,以满足一些特殊的需求
    2 在JDK1.2之前,在自定义类加载器时,总会去继承classLoader类并重写loadclass ()方法,从而实现自定义的类加载类,但是在JDK1.2之后,不再建议用户去覆盖loadclass ()方法,而是建议把自定义的类加载逻辑写在findclass ()方法中
  2. 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findclass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

classLoader类,它是一个抽象类,其后所有的类加载器都继承自classLoader (不包括启动类加载器

在这里插入图片描述

链接阶段的三个过程

验证

  • 确保class文件的字节流中包含的信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
  • 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

准备

  • 实例变量在准备阶段会赋初始默认值,在初始化阶段才真正赋值
  • 类变量(static修饰)分配在方法区中,而实例对象是随着对象的创建一起分配到java堆中
  • static final修饰的变量在编译的时候已经初始化了,在准备阶段就真正赋值了

解析

  • 将常量池内的符号引用转换为直接引用的过程。
  • 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。
  • 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
  • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT Methodref info等

初始化阶段概述

  • 初始化阶段就是执行类构造器方法())的过程

    • 此方法不需定义,是javac编译器自动收集类中所有实例变量的赋值动作和静态代码块中的语句合并而来,并且会保证构造器方法中指令会按照源文件中出现的顺序执行。
  • ()不同于类的构造器。(构造方法是虚拟机视角下的())

  • 若该类具有父类,JVM会保证父类的()先执行,再子类的()

  • 虚拟机会保证一个类的()方法在多线程下被同步加锁。

/*
    结果为100
*/
public static int a = 10;

static {
    a = 100;
}

public static void main(String[] args) {
    System.out.println(a);
}

// *********************************
/*
    结果为10
*/

static {
    a = 100;
}

public static int a = 10;

public static void main(String[] args) {
    System.out.println(a);
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页