类加载器和类加载过程

本篇主要内容:

  • JVM工作结构简图。
  • 类加载过程。
  • 类加载器(ClassLoader)。
  • 自定义类加载器实现步骤。

JVM工作结构简图

类加载过程

上图中我们看到了字节码文件经过类加载子系统加载到JVM中,那么现在我们在看类加载子系统中发生了什么。
先放一张类加载过程的简图。

其中包括了三个大步骤,他们分别是:

加载

加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象。

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

加载通常由类加载器完成,加载类的方式具体有以下几项:

  • 本地资源加载。
  • 网络加载。Web Applet。
  • zip压缩包加载。jar,war。
  • 运行时计算生成。动态代理技术。
  • 其他文件生成。JSP应用。
  • 从加密文件中读取,主要是为了防止class文件被反编译。
链接

链接分为三个步骤:

  • 验证
    确保class文件的字节流中包含信息符合虚拟机要求。
    主要包括,文件格式验证,元数据验证,字节码验证,符号引用验证。
  • 准备
    为类变量分配内存,并为变量赋零值。
    这里不包括用final修饰的static,因为final在变异的时候就会分配了,准备阶段会显式初始化。
    不会为实例变量分配初始化。
  • 解析
    将常量池内的符号引用变为直接引用。
初始化
  • 初始化阶段就是执行类构造器方法<clinit>()方法。
  • <clinit>()方法是由javac将类变量赋值动作和静态代码块中的语句合并自动产生的。当类不存在类变量和静态代码块时不会自动生成此方法。
  • 一个类只会被加载一次,<clinint》()方法是同步加锁的。(后付测试代码)

注:如果想看此方法,可以使用JClassLib软件或IDEA中的JClassLib插件。
附一张clinit<>()方法图和同步测试代码。

package cn.lele;public class ClinitTest02 {    public static void main(String[] args) {        Runnable r = ()->{            System.out.println(Thread.currentThread().getName() + "start");            DeadThread deadThread = new DeadThread();            System.out.println(Thread.currentThread().getName() + "finish");        };        Thread a = new Thread(r,"A");        Thread b = new Thread(r,"B");        a.start();        b.start();    }}class DeadThread {    static {        if (true) {            System.out.println(Thread.currentThread().getName() + "初始化当前类");            while (true) {            }        }    }}

图中我们看到在number声明之前我们就可以在同步代码块中为他赋值,这是因为在链接的准备阶段,number变量已经分配了内存空间并且赋予了零值。

类加载器(ClassLoader)

类加载过程的第一步加载,是需要类加载器来完成的,Java为我们提供了三个类加载器来完成这一步。

引导类加载器
  • 该引导器使用C/C++实现。
  • 该引导器负责加载JAVA的核心类库。
  • 加载扩展类加载器和应用程序加载器,并制定为他们的父加载器。
  • 出于安全考虑,只会加载包名为java,javax,sun等开头的类。
扩展类加载器
  • sun.misc.Launcher$ExtClassLoader实现
  • 从JDK安装目录的jre/lib/ext子目录下加载类库。
  • 父类加载器为null,因为正常情况下我们是获取不到引导类加载器的。
应用程序类加载器
  • 也是sun.misc.Launcher类中通过内部类的方式实现。
  • 负责classpath指定路径下的类加载,默认使用此加载器。

注:

1.JVM规范中提到,JVM只支持引导类加载器和自定义类加载器,规范将所有派生于抽象类ClassLoader的类加载器划分为自定义类加载器。2.引导类加载器 -> 扩展类加载器 -> 应用程序类加载器为上下级关系,并非继承关系。

自定义类加载器

为什么要自定义类加载器。

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

怎么自定义类加载器。

  • 继承ClassLoader类,将自定义的逻辑写在findClass()方法中。
  • 如果没有特别复杂的需求,可直接继承URLClassLoader类。

文笔有限,若有错误,还望指正,感激不尽。