JVM 学习——类加载机制

HotSpot是按需加载类,当需要使用类的时候才会加载类。

类的加载过程(类的生命周期)

  1. 加载: 查找并加载类的二进制数据到内存中,把类中的常量,方法等信息保存在方法区,将生成的Class对象保存在方法区(jdk8之后方法区存在于元空间内)

  2. 链接:

    • 验证:确保类的字节码符合jvm的规范。

    • 准备:为类的静态变量分配内存,并将其初始化默认值。int:0,long:0L,boolean:false,引用类型为null。

      public class Temo {
          
          // 常量a在编译阶段就直接赋值18了
          public static final int a = 18;
          
          // 静态变量b在准备阶段时赋值0,在后续的初始化阶段才会赋值18
          public static int b = 18;
          
          // 实例变量c,当对象被实例化时(被 new的时候)被分配内存和初始化
          public int c;
      }
      
    • 解析:把类中的符号引用转成直接引用(无需多做了解)。

  3. 初始化: 类加载的最后一步,jvm会根据编写的代码在类中执行静态方法和静态变量的初始化。

  4. 使用: 类加载完成后,类的实例可以被创建和使用,方法可以被调用,静态变量和静态方法可以被执行和访问。

  5. 卸载: 当类不再被使用且没有任何引用后,JVM的垃圾回收器会回收该类占用的内存资源,类的卸载是不可逆的。

    卸载条件:

    • 类的所有实例都被回收了,且类的Class对象也没有被引用。
    • 加载该类的类加载器被回收了。
加载链接验证准备解析初始化使用卸载

类加载器(ClassLoader)

类加载器分类

类加载器是实现类加载机制的核心组件,JVM中主要有三种类加载器:

  • 启动类加载器:用来加载Java核心内库,由JVM自身实现。
  • 扩展类加载器:用来加载Java扩展库,通常是jre/lib/ext目录下的类库。
  • 应用程序(系统)类加载器:用来加载用户类路径下的类,是默认的类加载器。

双亲委派

过程

  1. 委派请求: 当一个类加载收到一个类的加载请求(应用程序(系统)类加载器),通常不会直接去加载该类,而是向上委派到该类加载器的父类加载器(通常是扩展类加载器)。
  2. 逐级向上: 父类加载器接收到请求后,同样会将请求委派给它的父类加载器。这个过程会一直向上委派,直到委派到最顶层的启动类加载器。
  3. 加载类: 启动类加载器尝试去加载这个类。如果这个能成功加载,则加载过程结束。如果启动类加载器无法加载该类,则会向下返还给它的子类加载器。
  4. 逐级向下: 每一层的类加载器依次尝试加载该类,直到找到成功加载该类的类加载器。如果所有父类都无法成功加载该类,则由最初接收到请求的类加载器去尝试加载。
  5. 失败处理: 如果最初的接收到类加载请求的类加载器也无法加载该类,则抛出ClassNotFoundException异常。

优势

  1. 安全性: 可以保证核心内库不会被用户自定义的类所替代,确保了Java运行环境的安全性。
  2. 避免重复加载: 通过这样一层的向上委派,可以避免父类加载器已经加载过的类再次被加载,造成资源的浪费。

loadClass & findClass

loadClass & findClass 是两个在类加载过程中是非重要的方法。

loadClass

loadClass方法就是类加载器的入口方法,用于加载类。

代码的执行逻辑如下:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 检查类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 判断类加载器的父类加载器是否为空
                if (parent != null) {
                    // 使用父类加载器加载
                    c = parent.loadClass(name, false);
                } else {
                    // 如果父类加载器为空,则默认使用启动类加载器作为父加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                 // 如果父类加载器加载失败,则抛出ClassNotFoundException 异常
            }
             // 如果抛出ClassNotFoundException 异常,并且还没有被加载到,则调用自己的findClass()方法加载
            if (c == null) {
                
                c = findClass(name);
            }
        }
        return c;
    }
}

findClass

findClass方法是一个抽象方法,必须由子类实现,用于查找类的字节码并将其定义为类对象。其典型实现是:

  1. 根据类名查找对应的字节码文件。
  2. 读取字节码并将其转换为类对象。
protected Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] b = loadClassData(name);
    return defineClass(name, b, 0, b.length);
}

private byte[] loadClassData(String name) throws ClassNotFoundException {
    // 根据类名查找字节码文件并读取字节码
    // 这是一个示例实现,具体实现方式可能因需求而异
    InputStream inputStream = getClass().getResourceAsStream(name.replace('.', '/') + ".class");
    if (inputStream == null) {
        throw new ClassNotFoundException(name);
    }
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    int data = inputStream.read();
    while (data != -1) {
        byteArrayOutputStream.write(data);
        data = inputStream.read();
    }
    return byteArrayOutputStream.toByteArray();
}

自定义类加载器

在代码中自定义一个类加载器,继承ClassLoader,重写findClass方法实现加载自己的类加载逻辑,示例代码如下:

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 自定义类加载逻辑
    }
}

执行逻辑解释:

  1. 首先会调用loadClass方法,然后通过双亲委派机制委派给父类加载器去尝试加载类。
  2. 如果所有被委派的父类加载器都加载失败,则会抛出ClassNotFoundException异常,并且调用自定义类加载器的findClass方法。
  3. 调用findClass方法执行自定义的类加载逻辑,加载类的字节码并通过defineClass将其转换为Class对象。

Tomcat类加载器

Tomcat在Java原来的类加载器的基础上新增了几种类加载器:

  • Common类加载器: 加载存放在$CATALINA_HOME/lib目录下的类库,
  • Cataina类加载器: 加载Tomcat服务器本身的类,不会与应用程序类发生冲突。
  • Shared类加载器: 加载存放在$CATALINA_HOME/shared目录下的库,这些库可以被服务器中的所有web应用程序共享,但不是由Common类加载器所加载的。
  • Webapp类加载器: 打破了双亲委派机制,收到类加载器请求首先尝试自己去加载。 每个Web应用程序都有其自己的Webapp类加载器,负责加载Web应用的 WEB-INF/classesWEB-INF/lib 下的JAR文件中的类。这确保了应用之间的隔离性。