目录
- 1 JVM是什么
- 2 类加载系统
- 2.1 类的加载过程
- 2.2 类加载器
- 3 双亲委派机制
- 3.1 双亲委派机制介绍
- 3.2 双亲委派机制的优缺点
- 3.3 自定义类加载器实现双亲委派机制
1 JVM是什么
Java Virtual Machine(Java虚拟机)是java程序实现跨平台的⼀个重要的⼯具(部件)
HotSpot VM,相信所有Java程序员都知道,它是Sun JDK和OpenJDK中所带的虚拟机,也是
⽬前使⽤范围最⼴的Java虚拟机。
只要装有JVM的平台,都可以运⾏java程序。那么Java程序在JVM上是怎么被运⾏的?
通过介绍以下JVM的三个组成部分,就可以了解到JVM内部的⼯作机制
-
类加载系统:负责完成类的加载
-
运⾏时数据区:在运⾏Java程序的时候会产⽣的各种数据会保存在运⾏时数据区
-
执⾏引擎:执⾏具体的指令(代码)
2 类加载系统
类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件“。每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而-是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。
简单来说,类加载器的主要作用就是加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)
2.1 类的加载过程
⼀个类被加载进JVM中要经历哪⼏个过程
-
加载: 通过io流的⽅式把字节码⽂件读⼊到jvm中(⽅法区)
-
校验:通过校验字节码⽂件的头8位的16进制是否是java魔数cafebabe
-
准备:为类中的静态部分开辟空间并赋初始化值
-
解析:将符号引⽤转换成直接引⽤。——静态链接
-
初始化:为类中的静态部分赋指定值并执⾏静态代码块。
类被加载后,类中的类型信息、⽅法信息、属性信息、运⾏时常量池、类加载器的引⽤等信息会被加载到元空间中。
2.2 类加载器
类是谁来负载加载的?——类加载器。不同级别的类由不同的加载器加载
-
Bootstrap ClassLoader 启动类加载器:负载加载jre/lib下的核⼼类库中的类,⽐如rt.jar、charsets.jar
我们常用内置库 java.xxx.* 都在里面,比如java.util.、java.io.、java.nio.、java.lang.、java.sql.、java.math.。
-
ExtClassLoader 扩展类加载器:负载加载jre/lib下的ext⽬录内的类
ext 加载路径:System.getProperty("java.ext.dirs");
-
AppClassLoader 应⽤类加载器:负载加载⽤户⾃⼰写的类,加载当前应用 classpath 下的所有 jar 包和类。
app 加载路径:System.getProperty("java.class.path");
-
⾃定义类加载器:⾃⼰定义的类加载器,可以打破双亲委派机制。
由图可知,上一层加载器都会以父类的形式传入下一次加载。
3 双亲委派机制
3.1 双亲委派机制介绍
当类加载进⾏加载类的时候,类的加载需要向上委托给上⼀级的类加载器,上⼀级继续向上委托,直到启动类加载器。启动类加载器去核⼼类库中找,如果没有该类则向下委派,由下⼀级扩展类加载器去扩展类库中,如果也没有继续向下委派,直到找不到为⽌,则报类找不到的异常。
双亲委派机制的流程如下:
类加载的核心代码如下:
ClassLoader.class中的
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);
if (c == null) {
long t0 = System.nanoTime();
try {
//调用父类的加载器
if (parent != null) {
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;
}
}
3.2 双亲委派机制的优缺点
优点:
-
避免重复加载:通过委派给父类加载器,可以避免同一个类被多次加载,提高了加载效率。
-
安全性:通过双亲委派机制,核心类库由根加载器加载,可以确保核心类库的安全性,防止恶意代码替换核心类。
-
扩展性:开发人员可以自定义类加载器,实现特定的加载策略,从而扩展Java的类加载机制。
缺点:
-
灵活性受限:双亲委派机制对于某些特殊的类加载需求可能过于严格,限制了加载器的灵活性。
-
破坏隔离性:如果自定义类加载器不遵循双亲委派机制,可能会破坏类加载的隔离性,导致类冲突或安全性问题。
-
不适合动态更新:由于类加载器在加载类时会先检查父加载器是否已加载,因此在动态更新类时可能会出现问题,需要额外的处理。
总体而言,双亲委派机制通过层次结构和委派机制提供了一种有序、安全的类加载方式,但也存在一些限制和不适用的情况。
3.3 自定义类加载器实现双亲委派机制
有上面的源码可以看到,loadClass方法中会先看父类加载器中能不能调用,都不能在走findClass()
方法,调用自己的加载器。可见 自定义类加载器主要就是重写findClass方法,再此类中,loadClass方法没有被重写,就会执行父类ClassLoader中的loadClass方法,只要执行的是ClassLoader中的方法,就是实现的双亲委派。