共计 4559 个字符,预计需要花费 12 分钟才能阅读完成。
1、java
外面加载类的流程图。
次要步骤:
- 加载:在硬盘上查找并通过 IO 读取字节码文件,应用到类时才会加载,例如调用类的 main()办法,new 对象等等,在加载阶段会在内存中生成一个代表这个类的
java.lang.Class
对象,作为办法区这个类的各种数据的拜访入口。 - 验证:校验字节码文件的准确性。
- 筹备:给这个类的动态变量分配内存,并赋予默认值。
- 解析:将 符号援用 替换为间接援用,该阶段会把一些静态方法(符号援用,比方 main()办法)替换为指向数据所存内存的指针或句柄等(间接援用),这是所谓的 动态链接 过程(类加载期间实现),动静链接 是在程序运行期间实现的将符号援用替换为间接援用。
- 初始化:对类的动态变量初始化成指定值,执行动态代码块。
留神:主类在运行过程中如果应用到其它类,会逐渐加载这些类。jar 包或 war 包里的类不是一次性全副加载的,是应用到时才加载。
2、Java 中对应的类加载器
- 疏导类加载器::负责加载撑持
JVM
运行的,位于JRE
的lib
目录下的外围类库,比方rt.jar、charsets.jar
等。 - 扩大类加载器:负责加载撑持
JVM
运行的位于JRE
的lib
目录下的ext
扩大目录中的JAR
类包。 - 应用程序类加载器:负责加载
ClassPath
门路下的类包,次要就是加载你本人写的那些类。 - 自定义类加载器:负责加载用户自定义门路下的类包。
类加载器被创立的流程图:
Launch
类的相干源码如下:
// 调用 getLauncher 返回一个 Launcher 类的实例对象,该实例对象会由 JVM 进行创立
public static Launcher getLauncher() {return launcher;}
// 在创立 Launcher 实例对象的时候就会调用到该 构造方法
/**
* 1、*/
public Launcher() {
Launcher.ExtClassLoader var1;
try {
/**
* 创立了一个 ExtClassLoader (扩大类加载器),这个办法外面就会对 ExtClassLoader
* parent 属性 进行赋值操作,这里间接会赋值成 null
*/
var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);
}
try {
/**
* 创立了一个 AppClassLoader (扩大类加载器),这个办法外面就会对 AppClassLoader
* parent 属性 进行赋值操作,这里间接会赋值成 下面创立的 ExtClassLoader,并给该类的 * loader 属性 赋值为 AppClassLoader 的实例对象
*/
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);
}
// 设置以后线程的类加载器 为 AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
//。。。。。。省略一些不需关注代码
}
<u> 上述代码次要的逻辑就是去 创立了两个类加载器 ExtClassLoader
和 AppClassLoader
并别离赋值对应的 parent 的属性 为 null 和 ExtClassLoader
的实例对象。</u>
3、双亲委派机制
Java 外面进行类的加载次要应用的类是 java.lang.ClassLoader#loadClass(java.lang.String)
,所以要查看双亲委派机制是如何进行的,咱们须要看下 loadClass
办法对应的逻辑。loadClass
办法次要的源码如下:
public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {
// 这里首先会去判断以后类是否曾经被加载过了,如果曾经被加载过了,这里就不会返回 null
Class<?> c = findLoadedClass(name);
if (c == null) {long t0 = System.nanoTime();
try {
// 首先会判断以后类的父类加载器是否为空,第一次进来时咱们的类型加载器为 AppClassLoader,调用父类 ExtClassLoader.loadClass 而 ExtClassLoader 调用时 parent == null
if (parent != null) {c = parent.loadClass(name, false);
} else {
// 以后为 ExtClassLoader 对应的 parent 为 null
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();
// 如果后面对应的父类没有加载到时,就会调用 该 findClass 办法,该办法是个空实现,交由对应的子类去实现。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;
}
}
双亲委派机制说简略点就是先找父类加载,不行再由儿子本人加载。
咱们来看下应用程序类加载器 AppClassLoader
加载类的双亲委派机制源码,AppClassLoader
的 loadClass
办法最终会调用其父类 ClassLoader
的loadClass
办法,该办法的大体逻辑如下:
- 首先,检查一下指定名称的类是否曾经加载过了,如果加载过了,就不须要再加载,间接返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用
parent.loadClass(name,false);
)或者是调用 bootstrap 类加载器来加载。 - 如果父加载器及
bootstrap
类加载器都没有找到指定的类,那么调用以后类加载器的fIndClass
办法来实现类加载。
<u>从下面的代码中就能够看出,当调用 AppClassLoader
的 loadClass
办法的时候先会调用父类的,而后父类调用父类的 直到调用到 parent == null
,如果对应的父类没有加载到,则会由本身加载,这就是对应双亲委派的逻辑。</u>
4、为什么要设计双亲委派机制
- 沙箱平安机制:避免
JDK
外围API
类库能够不被内部所篡改,如咱们本人写的java.lang.String.class
类不会被加载。 - 防止类的反复加载:当父类曾经加载过一次,没有必要子类再进行加载一次,保障加载类的唯一性。
示例:
// 自定义 String 类,包名跟 自带的 String 保持一致,则会报上面的错。package java.lang;
public class String {public static void main(String[] args) {System.out.println("**************My String Class**************");
}
}
运行后果:谬误: 在类 java.lang.String 中找不到 main 办法, 请将 main 办法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩大 javafx.application.Application
5、自定义类加载器
由下面 ClassLoader
类的源码能够看出,自定义类加载器只须要继承 java.lang.ClassLoader
类,该类有两个外围办法,一个是 loadClass(String,boolean)
, 实现了 双亲委派机制,还有一个办法是 findClass
,默认实现是空办法,所以咱们自定义类加载器次要是 重写 findClass
办法。
示例:
// 待补充
5.1 突破双亲委派机制
再来一个沙箱平安机制示例,尝试突破双亲委派机制,用自定义类加载器加载咱们本人实现的java.lang.String.class
// 待补充
6、tomcat
中加载 jar 或者 war 包 是否能够应用双亲委派机制? 起因是什么?
从 tomcat 的实现来看,tomcat 中 负责加载对应 部署我的项目的 jar 或者 war 包中对应的类的自定义类加载器,是突破了双亲委派机制, 且每一部署在同一个 tomcat 的我的项目 对应的 tomcat 的自定义类加载器都是独立的,即每个我的项目都会有一个,起因如下:
先假如有两个我的项目,一个我的项目中用的是 spring-expression
的版本为 4.3 , 另外一个我的项目应用的 spring-expression
的版本为 5.5。
- 如果 tomcat 中应用了双亲委派机制,则当咱们须要加载
spring-expression
所应用到的类时,会将两个我的项目中先应用到的那个先加载(用到再加载),这样就会导致前面那个我的项目在加载时发现父类以及加载了,就不会去加载了,所以这里就会有问题了,就会导致前面这个我的项目所要应用到的spring-expression
中的版本不是它自身我的项目所指定的。 - 如果 tomcat 中部署的所有我的项目都专用同一个类加载器,同样也会呈现下面那个问题。
7、本文次要围绕上面 5 个环节发展:
java
外面是如何加载一个类的,次要的步骤有哪些,以及每一步的作用是什么?java
外面的类加载器有哪些,以及这些类加载器是如何被创立的流程?java
外面是如何实现双亲委派机制的?为什么要设计双亲委派机制?- 如何自定义一个类加载,并且实现自定义的类加载器突破对应的双亲委派机制。
- 思考,
tomcat
中加载 jar 或者 war 包 是否能够应用双亲委派机制? 起因是什么?