类加载机制

类加载的过程

类加载过程:JVM虚拟机把.class文件加载进内存,并进行解析成对应的class对象的过程,所以说类加载机制的最终的产品就是class对象。
举个通俗点的例子来说,JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。
所以,JVM不是一次性将所有的类加载进内存而是第一次遇到某个需要运行的类的时候才会加载。
类加载的过程主要分为三个阶段:加载-链接-初始化

加载

加载指的是将class字节码文件通过类加载器装入内存。
加载的过程分为三步:

  1. 通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件)。而获取的方式,通过jar包、war包、网络中获取、JSP文件生成等方式。
  2. 将Class文件转换为运行时数据结构(存储到方法区中)
  3. 在内存中生成一个代表这个类的java.lang.Class对象

类加载器

类加载器的作用就是将.class文件读入jvm内存。如果要比较两个类,首先比较其类加载器是否一致。
类加载器的种类
启动类加载器:负责加载lib包下的.class文件
拓展类加载器:负责加载lib/ext包下的.class文件
应用类加载器:负责加载自己写本地经过编译的java文件。
自定义加载器:按照自己的定义去加载.class文件,通过继承ClassLoader实现,重写findclass。比如我们可以对,class文件加密,在自定义加载器加载的时候实现解密。当我们从网络获取非标准字节码文件,可以使用自定义加载器来加载。
双亲委派模型:当一个收到一个加载请求时,首先将这个请求委派给父类,每一层的加载器都是如此。一直将请求委派到最顶层的启动类加载器,加载的时候,首先从启动类加载器开始,父加载器无法加载请求才会交给子加载器,然后依次递归。

自定义加载器如何创建?

自定义加载器通过继承java.lang.ClassLoader。
如果不想打破双亲委派,那么只需要重写findClass方法即可
如果想打破双亲委派,只需要重写loadclass。因为loadclass 就是双亲委派模型的源码,双亲委派模型的源码如下:

public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}

protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判断该类型是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
try {
if (parent != null) {
//如果存在父类加载器,就委派给父类加载器加载
c = parent.loadClass(name, false);
} else {
//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

java如何实现热部署?

什么是热部署?热部署就是在不启动jvm虚拟机的前提下,能够自动检测到.class的更新行为,从而加载最新的.class文件。虚拟机在启动时加载类,并读入jvm内存,当后期有一个class文件更新,默认的jvm虚拟机是不会重新加载的(因为内存已经存在了class对象,双亲委派机制是不会加载的class文件的)。所以要实现热部署,首先要打破双亲委派机制。
如何实现?

  1. 自己实现一个自定义类加载器继承ClassLoader,重写loadClass方法,
  2. 开启一个class文件监听,一旦有class文件更新,我们就之前的类和类加载器丢弃,使用新的类加载器去加载。

链接

验证

验证字节码文件的格式,保证符合jvm虚拟机的规范

准备

为类的静态变量分配内存,并且赋初值。这里的初值是默认值,比如int 默认为0,引用类型的初值默认为null。

解析

将常量池中的符号引用变成直接引用。比如person p p代表的就是符号引用,需要将堆内存中的地址赋给p,也就是符号引用变成直接引用。

初始化

对static修饰的变量或者代码块进行初始化赋值。

何时触发初始化

  1. new 一个对象
  2. 调用一个类的静态变量 以及静态方法的时候
  3. 初始化子类的时候,需要先初始化其父类的时候
  4. 使用反射方法

方法区的class什么时候被回收?

  1. 类加载器已经回收
  2. 这个类的所有实例已经被回收
  3. 这个类的class 没有被引用

参考

​类加载机制​​​​类加载器​​​​类加载过程​