JVM-类的加载机制

120 阅读4分钟

java.lang.ClassNotFoundException

这个异常作为从事java开发的同学应该是经常遇到。其背后就是java类的加载机制

前言

java是一门跨平台的程序设计语言,它可以实现一次编写,到处运行。 我们编写的java代码是如何实现到处运行呢?在解决这个问题之前,我们先要了解java中jdkjrejvm这三者之间的关系。

JDK、JRE、JVM的关系

  • JDK:Java开发工具包(Java Development Kit)
    JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。

  • JRE:Java运行环境(Java Runtime Environment)
    JRE可以让计算机系统运行Java应用程序。它包括Java虚拟机(jvm)、Java核心类库和支持文件。

  • JVM:Java虚拟机, 是用C语言编写的一个软件。主要作用是将字节码文件指令翻译成机器可以识别的指令。

jdk关系.png

java跨平台的原因

java在os操作系统之上提供了一个java运行环境,只要该平台安装了该运行环境,即可运行java程序。 java之所以能跨平台,是因为jvm(java虚拟机)可以跨平台。

111.png

java程序通过编辑器形成字节码文件.class,再经过jvm的解释形成最终的机器码,被操作系统执行。

类的加载过程

类的加载过程.png

  • 加载
  1. 通过全类名获取定义此类的二进制字节流
  2. 将字节流所代表的静态存储结构转换为方法区运行时数据结构
  3. 在堆生成一个代表该类的Class对象,作为方法区这些数据的访问入口
  • 验证
  1. 文件格式验证:字节流是否符合Class文件格式的规范
  2. 元数据验证:对javac编译阶段的语义分析,以保证描述的信息符合java语言的规范
  3. 字节码验证:跳转指令是否指向正确的位置,操作数类型是否合理
  4. 符号引用验证:符号引用的直接引用是否存在
  • 准备
    为class对象的静态变量分配内存并初始化。这里的初始化是 0、null、false这种

  • 解析
    将常量池内的符号引用转换为直接引用

  • 初始化
    这个阶段才是执行类中定义的java代码;初始化阶段是调用类构造器的过程

类加载器

jvm提供了3种类加载器

  • 启动类加载器(Bootstrap ClassLoader)
    用来加载java核心类库,无法被java程序直接引用。
    负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。

  • 扩展类加载器(Extension ClassLoader)
    用来加载java的扩展库,java的虚拟机实现会提供一个扩展库目录,该类加载器在扩展库目录中查找并加载java类。
    负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。

  • 应用程序类加载器(Application ClassLoader)
    根据java的类路径来加载类,一般来说,java应用的类都是通过它来加载。 负责加载用户路径(classpath)上的类库。JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader 实现自定义的类加载器。

  • 自定义类加载器(User ClassLoader)
    用户自己定义类加载器,实现ClassLoader

双亲委派

所谓的双亲委派机制,就是当一个类收到类加载请求后,会把这个请求依次传递给父类加载器,如果顶层的可以加载,就返回成功,否则,在依次传给子类进行加载。

双亲委派模型.png

为什么需要双亲委派

为了防止内存中出现多个相同的字节码。因为如果没有双亲委派的话,用户就可以自己定义一个java.lang.String类,那么就无法保证类的唯一性。

双亲委派机制的作用

  • 避免类的重复加载
  • 保证Java核心类库的安全

破坏双亲委派机制

双亲委派模型并不是一个强约束的模型,在一些情况下是可以被破坏的。
只要重写loadClass方法就可以被破坏。

  • Tomcat场景打破双亲委派机制
    一个web容器可能部署多个应用,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器上只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
    Tomcat为了实现隔离性,内置了几个类加载器,每个webappClassLoader加载自己目录下的class文件,不会传递给父类加载器,打破双亲委派机制。

  • JDBC场景打破双亲委派机制
    在获取jdbc连接的驱动管理类中,优先使用当前线程的类加载器而不是自身使用的类加载器来加载。

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");

image.png

image.png

image.png