java.lang.ClassNotFoundException
这个异常作为从事java开发的同学应该是经常遇到。其背后就是java类的加载机制
前言
java是一门跨平台的程序设计语言,它可以实现一次编写,到处运行。 我们编写的java代码是如何实现到处运行呢?在解决这个问题之前,我们先要了解java中jdk、jre、jvm这三者之间的关系。
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语言编写的一个软件。主要作用是将字节码文件指令翻译成机器可以识别的指令。
java跨平台的原因
java在os操作系统之上提供了一个java运行环境,只要该平台安装了该运行环境,即可运行java程序。 java之所以能跨平台,是因为jvm(java虚拟机)可以跨平台。
java程序通过编辑器形成字节码文件.class,再经过jvm的解释形成最终的机器码,被操作系统执行。
类的加载过程
- 加载
- 通过全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区运行时数据结构
- 在堆生成一个代表该类的Class对象,作为方法区这些数据的访问入口
- 验证
- 文件格式验证:字节流是否符合Class文件格式的规范
- 元数据验证:对javac编译阶段的语义分析,以保证描述的信息符合java语言的规范
- 字节码验证:跳转指令是否指向正确的位置,操作数类型是否合理
- 符号引用验证:符号引用的直接引用是否存在
-
准备
为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
双亲委派
所谓的双亲委派机制,就是当一个类收到类加载请求后,会把这个请求依次传递给父类加载器,如果顶层的可以加载,就返回成功,否则,在依次传给子类进行加载。
为什么需要双亲委派
为了防止内存中出现多个相同的字节码。因为如果没有双亲委派的话,用户就可以自己定义一个java.lang.String类,那么就无法保证类的唯一性。
双亲委派机制的作用
- 避免类的重复加载
- 保证Java核心类库的安全
破坏双亲委派机制
双亲委派模型并不是一个强约束的模型,在一些情况下是可以被破坏的。
只要重写loadClass方法就可以被破坏。
-
Tomcat场景打破双亲委派机制
一个web容器可能部署多个应用,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器上只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
Tomcat为了实现隔离性,内置了几个类加载器,每个webappClassLoader加载自己目录下的class文件,不会传递给父类加载器,打破双亲委派机制。 -
JDBC场景打破双亲委派机制
在获取jdbc连接的驱动管理类中,优先使用当前线程的类加载器而不是自身使用的类加载器来加载。
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");