热衷学习,热衷生活!😄

沉淀、分享、成长,让自己和他人都能有所收获!😄

一、JVM类加载过程

JVM类加载过程如下图:

JVM类加载过程分为:加载链接初始化使用卸载 这五个阶段,其中链接阶段又包括:验证准备解析

  • 加载 :通过类的完全限定名,查找此类的二进制字节码文件,通过该字节码文件创建Class对象。
  • 链接 :包含验证准备解析 三个阶段:
    • 验证 :确保Class文件复合虚拟机规定的Class文件格式,包含文件格式验证、元数据验证、字节码验证、引用符号验证。
    • 准备 :为类的静态变量分配内存并设置初始化值,注:这里不包含`final`修饰的静态变量,因为`final`修饰的静态变量是在编译期分配。
    • 解析 :将常量池的间接引用转换为直接引用,解析包含字段解析、接口解析、方法解析。
  • 初始化 :初始化静态变量和静态块,先初始化父类,再初始化当前类,只有对类主动时才会初始化。
  • 使用 :程序代码执行时使用,new出对象程序中使用。
  • 卸载 :程序代码退出、异常、结束等,执行垃圾回收。

二、类加载时机

  • 创建类的实例,也就是new一个对象。
  • 访问类的静态方法或者静态变量(包含静态变量赋值)。
  • 使用Class.forName()反射类。
  • 子类初始化的时候。
  • JVM启动时标明的启动类。

三、类加载器

类加载器包括启动类加载器、扩展类加载器、系统类加载器、自定义类加载器四种加载器。

  • 启动类加载器(Bootstrap ClassLoader):负责加载Java类的核心类,是用原生代码实现。下面代码可以获得启动类加载器所加载的Java核心类库。

    1
    2
    3
    4
    URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
    for(URL url : urLs){
    System.out.println(url.toExternalForm());
    }

    输出如下:

    1
    2
    3
    4
    5
    6
    7
    8
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/resources.jar
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/rt.jar
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/sunrsasign.jar
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/jsse.jar
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/jce.jar
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/charsets.jar
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/jfr.jar
    file:/D:/Develop%20Tools/Java/jdk-8u231/jre/classes
  • 扩展类加载器(Extensions ClassLoader):负责加载JRE的扩展目录lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为Null。

  • 系统类加载器(System Class Loader):负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性。可以通过ClassLoader.getSystemClassLoader()方法获取当前系统类加载器,一般情况是自定义类加载器的父加载器。由Java语言实现,父类加载器为扩展类加载器。

    1
    2
    System.out.println(ClassLoader.getSystemClassLoader());
    System.out.println(ClassLoader.getSystemClassLoader().getParent());

    上面代码输出为:

    1
    2
    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@4eb7f003
  • 自定义类加载器(Custom ClassLoader):用户可以通过继承ClassLoader类实现自定义类加载器。由Java语言实现,自定义类加载器的父类是系统类加载器。

四、类加载机制

JVM的类加载机制主要有全盘负责、双亲委派、缓存机制三种加载机制。

  • 全盘负责:当一个类加载器加载某个Class时,该Class所依赖和引用其他Class也会由该类加载器负责加载,除非指定了使用其他类加载器加载。
  • 双亲委派:先让父类加载器加载该Class,只有在父类加载器无法加载该类时才尝试使用自己的类加载器加载。通俗的讲就是在某个特定的类加载器接到加载类请求时,先寄托给父加载器加载,依次递归,如果父加载器可以加载时则成功返回,如果不可以加载就自己去加载。
  • 缓存机制:缓存机制会确保所有加载过的Class都被会缓存,当程序中需要某个Class时,类加载器先从缓存区中搜索该Class,只有缓存区中不存在该Class对象时,系统才会读取该类的二进制数据,并将其转换为Class对象,存入缓存区。这就是为什么我们修改Class文件之后,必须重启JVM才会生效的原因。

其中双亲委派机制优势:

  • 父类加载器成功加载则返回,子类加载器不会再加载,防止了重复加载。
  • 防止核心API库被随意篡改。比如有一个要加载java.lang.Integer类的请求,通过双亲委派进制加载传递到启动类加载器,在在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,可以防止核心API被随意篡改。