【JVM】一篇文章让你彻底了解类加载器
JVM(三·上)一篇文章让你彻底了解类加载器
JVM(三·下)学会了自定义类加载器,以后的每一个类我都要自己加载进内存
一、什么是类加载子系统
类加载子系统就是一个负责管理.class文件加载到内存中的系统,属于JVM系统的子模块,所以叫做子系统,当需要加载一个类时,JVM就会直接调用类加载子系统来进行加载
打个比方,JVM是一间公寓的主人,而类加载子系统就是看门的保安,所有客人(Class类)想要进入公寓都要经过大门,经过大门就需要保安来开门接待
二、类加载的过程
Class类的生命周期示意图

1.加载
加载就是将.class文件通过字节流从磁盘读取到内存中,需要通过类的全限定名读取(就是文件夹中的路径),然后将这些数据存放在方法区中,.这就得到了一个Class类文件信息,然后类加载器会在堆中创建一个对象来指向这个信息,这个对象就是我们反射获取的对象,作为方法区的入口,这个就是Class对象了
如果.class文件中包含的信息还不知道的话,可以去看我的这篇文章JVM(一)带你解读字节码,加载这个阶段也是唯一我们能够触碰到的阶段了
2. 验证
验证就是确保加载进来的Class类文件信息不会危害到JVM系统。主要有以下四种验证
- 文件格式验证。验证JVM是否支持该Class类文件信息的魔数和版本信息, 常量池中是否有JVM不支持的数据类型
- 元数据验证。对字节码描述的信息进行语义分析,如这个类是否有父类,是否继承了不能被继承的类等
- 字节码验证。这个验证就是对剩下的字节码信息做语法验证
- 符号引用验证。验证该Class类文件信息中的其他类的全限定名是否正确,访问其他类的属性或方法是否存在,访问权限是否符合
3.准备
准备就是给静态变量在方法区中分配内存,并且赋予默认值。静态变量就是带有static关键字的属性
4.解析
解析就是将Class类文件信息中的符号引用(对常量池中个数据的引用)转换为直接引用,这时的Class类文件信息就已经跟我们反编译得到的文件差不多了
5.初始化
初始化就是调用Class类文件信息中的**()方法**,这个方法的内容是,编译器自动收集类中所有类变量的赋值动作和静态代码块而产生的方法,类加载器执行的顺序是先赋值再静态代码
Class类的初始化时机
Class类的初始化时机跟Class类的加载时机十分的相像,但是它们之间是有一个先后顺序的,加载在初始化的前面
除了基本数据类,其他类都不是一开始就全部加载进内存的,一个类想要加载到JVM中,只有主动引用Class类的时候,才会执行初始化,被动引用Class类则不会初始化
主动引用Class类的情况:
- 遇到 new、getstatic、putsatic 或 invokestatic 这 4 条字节码指令时。通俗点说,就是分别在new一个对象、修改静态属性、调用静态方法时,就会触发Class类的初始化
- 通过反射机制调用Class对象时。在通过反射机制调用Class对象时,如果在内存中没有这个Class对象,就会立刻让类加载子系统去加载并创建这个Class对象
- 实例化的对象有父类时。在实例化一个对象时,如果发现它的父类的Class对象不在内存中时,则会触发Class类的初始化
- 当运行main()方法时。当运行main方法时,主动地对main方法所在的类进行初始化
- 在使用方法句柄时。方法句柄就是根据返回类型和参数类型创建一个MethodType对象,然后在根据这个对象加上类.class、方法创建一个MethodHandle对象,通过这个对象就能不创建类,直接调用这个类的方法。在这种情况下,也是会触发类的初始化
被动引用Class类的情况:
- 通过子类调用父类的静态属性。在使用子类调用父类上的静态属性时,不会触发子类的加载,而是会触发父类的初始化
- 定义类数组时。在创建一个用来存放对象的数组时,不会出发类的初始化,因为在数组中存放的实际上是地址,不是类
- 引用类的常量。常量指的是有static final关键字的属性
使用&卸载这两个过程就不是类加载器能够管的了,而是有JVM进行控制
三、类加载器
1.类加载器的种类
- 启动类加载器(Bootstrap ClassLoader)
只负责加载JRE 核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar或sun.boot.class.path路径下的jar包),启动类加载器没有父类加载器,不是继承于ClassLoader的,这个类是用C/C++实现的,嵌套在JVM内部 - 扩展类加载器(Extension ClassLoader)
只负责加载JRE 扩展 目录ext中的jar包,扩展类加载器派生于ClassLoader(就是跟它相似),它的父类加载器是启动类加载器(这里不是指继承,看到双亲委派机制就知道了),这个类是用Java实现的 - 系统类加载器(Application ClassLoader)
负责加载ClassPath路径下的类包,也就是我们编写的类和第三方类,它的父类加载器是扩展类加载器,这个类是用Java实现的 - 自定义类加载器,我们可以通过继承ClassLoader实现,一般是用来加载我们编写的类
我们能够触碰的只有自定义类加载器,其它的都是有Java定义好的了
2.双亲委派机制
在自定义类加载器之前,有必要先了解一下双亲委派机制,这样有助于我们熟悉ClassLoader和三个类加载器之间的关系
机制概念
就是每个类加载器都会先自己找一下有没有加载过这个类,有就不加载直接返回,没有就调用父类的方法去查找有没有加载过这个类;父类如果加载过就会抛出异常,没有就先尝试自己加载,加载不了就会让子类加载器加载,然后一直往下。

好处
通过一层层地询问,能够防止核心的类被破坏、被篡改、被加载多次,有效地保证了类库的稳定性
源码分析
截ClassLoader中的loadClass方法,我在下面代码加点注释
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//获取锁
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//首先,检查类是否已加载
Class<?> c = findLoadedClass(name);
//如果为null,则没有加载过
if (c == null) {
long t0 = System.nanoTime();
try {
//判断是否有父类加载器
if (parent != null) {
//有就让父类加载器去执行它的loadClass方法,重复走一边流程
c = parent.loadClass(name, false);
} else {
//没有就直接调用这个方法,让启动类加载器去检查是否有加载过
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//父类找不到、也加载不了,尝试自己加载
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//我也不知道干嘛的方法
resolveClass(c);
}
return c;
}
}

浙公网安备 33010602011771号