三、类加载器

52 阅读5分钟

类加载器

字节码文件被JVM加载执行过程分成三部分:

  1. 加载:字节码文件会被类加载器加载到内存中,并且生成指向字节码文件内容的引用,这个引用是类对象,存放在方法区中。
  2. 链接:链接过程又可以划分成三个小过程
    • 校验:对字节码文件内容进行校验
    • 准备:给字节码文件中的静态变量赋默认值
    • 解析:将字节码文件中的符号引用转换成地址引用,主要是类对象、方法和属性的符号引用转换
  3. 初始化:给字节码文件中的静态变量赋初始值,并且执行静态代码块

image.png

类加载器分类

字节码文件被JVM加载的过程是由类加载器去完成的。类加载器可以分成四类,分别是:Bootstrap、Extension、App和自定义类加载器。

  • Bootstrap:加载jre/lib文件夹下的rt.jar、charset.jar等核心类
  • Extension:加载jre/lib/ext文件夹下的所有类,这些是扩展类
  • App:加载classpath指定内容
  • 自定义类加载器:一般用来加载程序员自己写的类

image.png

Extension、App以及自定义类加载器也是类,它们是由Bootstrap加载的。

双亲委派原则:

类加载器进行类加载的过程叫做双亲委派,大致流程如下:

  1. 如果有自定义类加载器,先查询自定义类加载器缓存,看是否已经加载过,如果加载过直接返回,如果没有,继续后面的流程
  2. 查询App类加载器缓存,看是否已经加载过,如果加载过直接返回,如果没有,继续后面的流程
  3. 查询Extension类加载器缓存,看是否已经加载过,如果加载过直接返回,如果没有,继续后面的流程
  4. 查询Bootstrap类加载器缓存,看是否已经加载过,如果加载过直接返回,如果没有,继续后面的流程
  5. 当发现这个类没有被加载过,首先准备由Bootstrap进行加载,但是Bootstrap只加载核心类,所以要判断此类是否是核心类。如果是,则加载,如果不是,委派给Extension类加载器进行加载
  6. Extension类加载器准备加载,由于其只加载扩展jar包,所以要判断此类是否属于扩展jar包中的类。如果是,则加载,如果不是,委派给App类加载器进行加载
  7. App类加载器准备加载,由于其只加载classpath路径下的类,所以要判断此类是否属于classpath路径下的类。如果是,则加载,如果不是,委派给自定义类加载器进行加载
  8. 最后由自定义类加载器加载

设置双亲委派机制的目的是为了安全考虑。如果程序员写的类,其类名与java的核心类名相同,在没有双亲委派机制的情况下,其被加载进来了。那么,这个类很容易会被其他程序员用到,也很容易引发安全问题。

自定义类加载器

我们可以通过观察App类加载器是如何实现的,以此来自定义类加载器。App类加载器的类名是AppletClassLoader,其继承了URLClassLoader,而URLClassLoader继承了SecureClassLoader,SecureClassLoader最后继承了ClassLoader抽象类。所以,我们自定义类加载器也要继承ClassLoader。

类加载器在加载类的时候,需要执行loadClass方法,我们观察App类加载器的loadClass方法逻辑:

image.png

App类加载器的loadClass方法中会调用父类的loadClass方法,我们点进去发现是ClassLoader的loadClass方法

image.png

查看代码的逻辑,发现App类加载器首先会从自己缓存中获取类对象,如果没有,则将此类的加载任务交给父类加载器,如果父类加载器没有加载完成,App类加载器会调用findClass方法,尝试自己加载此类。

image.png

App类加载器调用了其父类的findClass方法,我们点进去

image.png

findClass方法中是调用defineClass方法加载类。所以,我们自定义类加载器时,loadClass和defineClass方法已经被ClassLoader类实现了,我们不用实现,而findClass方法需要我们实现。

自定义类加载器继承ClassLoader:

image.png

懒加载

java的类在加载的时候是按需加载,即需要用到的时候,JVM才会将其加载到内存中,这个机制叫做懒加载。

之所以采用懒加载的方式,是因为java程序在执行的时候,需要加载核心类、第三方类以及程序员自己写的类,所有类都加载到内存中,而有些类可能永远都用不到,这对内存资源是极大的浪费。所以,为了提高内存利用率,采用懒加载的方式。

类加载的初始化

java程序的字节码文件由JVM加载到内存要经历几个过程:加载、校验、准备、解析、初始化。

加载:将字节码文件内容加载到内存中

校验:对字节码文件内容的格式和语法进行检查、校验是否正确

准备:给静态变量分配内存空间,并且赋默认值

解析:将常量池中的符号引用替换成直接引用

初始化:给静态变量赋初始值,并且执行静态代码块和静态方法

我们可以看到,在准备阶段,只给静态变量分配了内存空间,那么成员变量什么时候分配内存空间呢?

成员变量只会在创建对象的时候,在堆中分配内存空间。所以,创建一个对象的常规流程应该是,在堆区开辟一块内存空间,初始化内存空间中的对象信息,然后将这个内存空间地址赋值给对象变量。