Java基础:Java类加载器和加载机制
类加载器就是寻找类的字节码文件并构造出类在JVM内部表示对象的组件。
类加载工作由ClassLoader
及其子类负责。ClassLoader
是一个重要的Java
运行时系统组件,它负责在运行时查找和装入Class
字节码文件。
类加载器
负责将.class
文件加载到内存中,并为之生成对应的 Class
对象。
类加载器层次结构
- Bootstrap ClassLoader 根类加载器
- 也被称为引导类加载器,负责Java核心类的加载。此加载器是
C
写的,在Java是看不到的。 - 加载Java jdk核心类JRE/lib/rt.jar,如System,String类。
- 也被称为引导类加载器,负责Java核心类的加载。此加载器是
- Extension ClassLoader 扩展类加载器
- sun.misc.Launcher$ExtClassLoader
- 负责加载JRE/lib/下的扩展目录ext中的jar类包。
- Sysetm ClassLoader 系统类加载器
- 也称为应用类加载器,sun.misc.Launcher$AppClassLoader。
- 负责在 JVM 启动时加载来自 java 命令的 class 文件,以及 Classpath 环境变量所指定的 jar 包和类路径。
类加载器的流程
在Java中,类加载器把一个类装入JVM,需要经过加载、链接(校验,准备,解析)、初始化
步骤。
加载
把类的.class
文件的字节码加载到内存中,有两种方式:
- 隐式加载:不通过代码里调用
ClassLoader
来加载需要的类。而是通进JVM
来自动加载需要的类到内存。 - 显式加载:调用
ClassLoader
类来加载一个类,可以使用以下方法:- this.getClass.getClassLoader.loadClass(String name)
- Class.forName(String className)
- 或自己实现ClassLoader的 findClass()方法等。
连接
把类的二进制合到到JRE中,分三个阶段
- 验证:检验加载的类内部结构是否正确,并和其他类协调一致。
- 准备:为类静态变量分配内存,并设置默认值(仅包含类静态变量,不包含实例变量)。
- 解析:虚拟机将常量池中的符号引用替换为直接引用。
初始化
类静态变量初正确的初始值,执行静态代码块,执行构造方法等。
- 类变量赋初始值:
- 声明类变量时指定初始化值。
- 使用静态初始化块为类变量指定初始值。(静态初始化块都将被当成类的初始化语句)
- JVM初始化步骤:
- 若类没加载和连接,则先加载并连接该类。
- 若类的直接父类没有被初始化,则先初始化其直接父类。
- 若有初始化语句,则依次执行这些初始化语句
- 类初始化时机
- 创建类的实例。
- 调用某个类的静态方法。
- 访问某个类或接口的类变量,或为该类变量赋值。
- 使用反身来强制创建该类或接口对应的java.lang.Class对象。
- 初始化某个类的子类,该子类的直接父类和所有间接父类都被初始化。
- 直接使用java.exe来运行某个主类。
- final修饰的类变量的初始化:
- 如果类变量(静态变量)使用
final
修饰,并且它的值在编译时就确定下来,该变量相当于常量, 不会被初始化。static final String name = “Admin”; 。 - 如果使用
final
修饰类变量的值在不能在编译时确定下来,必须等到运行时才可以确定该类变量的值,则会被初始化。static final String nowtime = System.currentTimeMillis() + “”; 。
- 如果类变量(静态变量)使用
使用ClassLoader
类的loadClass()
方法来加载某个类时,该方法只加载该类;使用Class的**forName()**静态方法会强制初始化该类。
类加载器机制
Java中,一个类用其全限定类名(包括包名和类名)作为唯一标识。
JVM中,一个类用其全限定类名和其类加载器作为唯一标识(类名、包名、加载器的实例)。
- 全盘负责制:该类所有依赖的和引用的其他类将由该类的加载器负责载入,除非显式使用另外的加载器载入。
- 父类委托:先委托父类加载器加载目标类,找不到时再从自己的类路径中查找并加载目标类,确保类只被加载过一次。
- 缓存机制:保证所有加载过的类都会被缓存,当需要使用时,先从缓存区中搜索,缓存不存在该类时,系统才会读取该类对应的二进制数据,将期转换成
Class
对象,放入缓存区中。
类加载器执行步骤
类加载器加载Class大致经过如下8个步骤:
- 检测此类是否载入过(即在缓存区是否存),有进入第8步,没有执行第2步。
- 如果父类加载器存在,执行第3步;否则跳到第4步执行。
- 请求父类加载器载入目标类,如果成功跳到第8步,否则执行第5步。
- 请求根类加载器载入目标类,如果成功跳到第8步,否则执行第7步。
- 当前类加载器尝试寻找Class文件,如果找到则执行第6步,否则跳到第7步。
- 从文件中载入Class,成功载入后跳到第8步。
- 抛出ClassNotFoundException异常。
- 返回对应的java.lang.class对象
自定义的类加载器
通过扩展
ClassLoader
的子类,并重写该ClassLoader
所包含的方法来实现自定义的类加载器。- loadClass(String name,boolean resolve):ClassLoader方法入口,根据指定名称为加载类。
- findClass(String name)根据指定定名称来查找类。建议使用
- Class defineClass(String name,byte[] b, int off, int len)
- defineClass()
- findSystemClass(String name)
- static getSystemClassLoader()
- getParent()
- findLoadedClass(String name)
使用自定义类加载器实现如下常见功能:
- 执行代码前自动验证数字签名。
- 根据用户提供的密码解密代码,从而可以实现代码混淆来避免反编译
*.class
文件。 - 根据用户需求来动态在加载类。
- 根据应用需求把其他数据以字节码的形式加载到应用中。
URLClassLoader类
该类也是系统类加载器和扩展类加载器的父类。功能比较强大,可以从本地文件或远程主机获取二进制文件来加载类。- **URLClassLoader(URL[] URLS)**:使用默认的父类加载器创建一个
ClassLoader
,该对象将从urls所指定的系列路径来查询并加载类。 - **URLClassLoader(URL[] urls, ClassLoader parent)**:使用指定的父类加载器创建一个
ClassLoader
对象,其他功能与个构造器相同。
- **URLClassLoader(URL[] URLS)**:使用默认的父类加载器创建一个
一旦得到URLClassLoader
对象之后,就可以调用该对象的loadClass()
方法来加载指定类。例如:可以直接从文件系统中加载MySQL驱动,并使用该驱动来获取数据库连接,就无须将驱动添加到CLASSPATH环境变量中。
示例代码
示例一:获取加载器
1 | public class Demo2 { |
示例二:URLClassLoader
1 | public class UrlClassLoadTest { |
相关参考
Java基础:Java类加载器和加载机制