Java虚拟机详解(十一)------双亲委派模型
在上一篇博客,我们介绍了类加载过程,包括5个阶段,分别是“加载”,“验证”,“准备”,“解析”,“初始化”,如下图所示:

本篇博客,我们来介绍Java虚拟机的双亲委派模型,在介绍之前,我先抛出一个问题:
我们知道,在JDK源码中,有各种Java自带的类,比如java.lang.String,java.util.List等,那么我们自己的项目中,能够写一个命名为java.lang.String.java 等JDK源码中存在的类,并且在项目中使用吗?
1、类加载器
什么是类加载器?上篇博客我们介绍类加载过程中的第一个阶段——加载,作用是“通过一个类的全限定名来获取描述此类的二进制流”,那么这个加载过程就是由类加载器来完成的。
从Java虚拟机的角度出发,只存在两种不同的类加载器,一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++ 语言实现,是虚拟机自身的一部分;另一种是所有其它的类加载器,这些类加载器都是由Java语言实现的。但是从Java开发人员的角度来看,类加载器可以细分为如下四种:
①、启动类加载器(Bootstrap ClassLoader)
负责将存放在 <JAVA_HOME>/lib 目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是虚拟机按照文件名识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
启动类加载器无法被Java程序直接引用。
JDK 中的源码类大都是由启动类加载器加载,比如前面说的 java.lang.String,java.util.List等,需要注意的是,启动类 main Class 也是由启动类加载器加载。
②、扩展类加载器(Extension ClassLoader)
这个类加载器由 sun.misc.Launcher$ExtClassLoader 实现,负责加载<JAVA_HOME>/lib/ext 目录中的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库。
开发者可以直接使用扩展类加载器。
③、应用程序类加载器(Application ClassLoader)
由 sun.misc.Launcher$AppClassLoader 实现。由于这个类加载器是 ClassLoader.getSystemClassLoader() 方法的返回值,所以一般也称它为系统类加载器。
它负责加载用户类路径ClassPath上所指定的类库,开发者可以直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
通常项目中自定义的类,都会放在类路径下,由应用程序类加载器加载。
④、自定义类加载器(User ClassLoader)
这是由用户自己定义的类加载器,一般情况下我们不会自定义类加载器,但有些特殊情况,比如JDBC能够通过连接各种不同的数据库就是自定义类加载器来实现的,具体用处会在后文详细介绍。
2、双亲委派模型
回到文章开头提出的问题,如果有不法分子在你项目中构造了一个java.lang.String类,并在该类中植入了一些不良代码,但你自己浑然不知,以为使用的String类还是 rt.jar 包下的,那可能会给你系统造成不良的影响。
聪明的Java虚拟机实现者也想到了这个问题,于是,他们引入了 双亲委派模型来解决这个问题。
下面是双亲委派模型的加载流程机制:

总结来说:双亲委派机制就是如果一个类加载器收到了类加载请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈到无法完成这个加载请求(它的搜索范围没有找到这个类),子加载器才会尝试自己去加载。
其实,这里叫双亲委派可能有点不妥,因为按道理来讲只有父加载器,这里的“双亲”是“parents”的直译,并不表示汉语中的父母双亲。另外,这里的父加载器也不是继承的关系。
/**
* Create by YSOcean
*/
public class ClassLoadTest {
public static void main(String[] args) {
ClassLoader classLoader1 = ClassLoadTest.class.getClassLoader();
ClassLoader classLoader2 = classLoader1.getParent();
ClassLoader classLoader3 = classLoader2.getParent();
System.out.println(classLoader1);
System.out.println(classLoader2);
System.out.println(classLoader3);
}
}
输出为:

那么知道了什么是双亲委派机制,双亲委派机制有什么好处呢?
回到上面提出的问题,如果你自定义了一个 java.lang.String类,你会发现这个自定义的String.java可以正常编译,但是永远无法被加载运行。因为加载这个类的加载器,会一层一层的往上推,最终由启动类加载器来加载,而启动类加载的会是源码包下的String类,不是你自定义的String类。
3、双亲委派模型实现源码
可以打开 java.lang.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);
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;
}
}
实现方式很简单,首先会检查该类是否已经被加载过了,若加载过了直接返回(默认resolve取false);若没有被加载,则调用父类加载器的 loadClass方法,若父类加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,则在抛出 ClassNotFoundException 异常后,在调用自己的 findClass 方法进行加载。
4、自定义类加载器
先说说我们为什么要自定义类加载器?
①、加密
我们知道Java字节码是可以进行反编译的,在某些安全性高的场景,是不允许这种情况发生的。那么我们可以将编译后的代码用某种加密算法进行加密,加密后的文件就不能再用常规的类加载器去加载类了。而我们自己可以自定义类加载器在加载的时候先解密,然后在加载。
②、动态创建
比如很有名的动态代理。
③、从非标准的来源加载代码
我们不用非要从class文件中获取定义此类的二进制流,还可以从数据库,从网络中,或者从zip包等。
明白了为什么要自定义类加载器,接下来我们再来详述如何自定义类加载器。
通过第 3 小节的 java.lang.ClassLoader 类的源码分析,类加载时根据双亲委派模型会先一层层找到父加载器,如果加载失败,则会调用当前加载器的 findClass() 方法来完成加载。因此我们自定义类加载器,有两个步骤:
1、继承 ClassLoader
2、覆写 findClass() 方法
Java虚拟机详解(十一)------双亲委派模型的更多相关文章
- java虚拟机类加载机制和双亲委派模型
java虚拟机类加载机制:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型. 类的生命周期是从类被加载到虚拟机内存中,到卸 ...
- Java虚拟机类加载器及双亲委派机制
所谓的类加载器(Class Loader)就是加载Java类到Java虚拟机中的,前面<面试官,不要再问我"Java虚拟机类加载机制"了>中已经介绍了具体加载class ...
- Java虚拟机详解----JVM常见问题总结
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- java虚拟机详解
注: 此篇文章可以算是读<深入理解Java虚拟机:JVM高级特性与最佳实践>一书后的笔记总结加上我个人的心得看法. 整体总结顺序沿用了书中顺序,但多处章节用自己的话或直白或扩展的进行了重新 ...
- Java自定义类加载器与双亲委派模型
其实,双亲委派模型并不复杂.自定义类加载器也不难!随便从网上搜一下就能搜出一大把结果,然后copy一下就能用.但是,如果每次想自定义类加载器就必须搜一遍别人的文章,然后复制,这样显然不行.可是自定义类 ...
- Java虚拟机详解02----JVM内存结构
主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启动时,是由java命令/javaw命令来启动的. 二.JVM基本结构: JVM基本结构图: ...
- Java虚拟机详解----JVM内存结构
http://www.cnblogs.com/smyhvae/p/4748392.htm 主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启 ...
- Java 虚拟机详解
深入理解JVM 1 Java技术与Java虚拟机 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言.Java类文件格式.Java虚 ...
- Java虚拟机详解04----GC算法和种类【重要】
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
随机推荐
- Android ListView性能优化实例讲解
前言: 对于ListView,大家绝对都不会陌生,只要是做过Android开发的人,哪有不用ListView的呢? 只要是用过ListView的人,哪有不关心对它性能优化的呢? 关于如何对ListVi ...
- Websocket 单聊功能
单聊代码 import json from flask import Flask,request,render_template from geventwebsocket.handler import ...
- 2018-8-10-WPF-使用-VisualStudio-2017-项目文件
title author date CreateTime categories WPF 使用 VisualStudio 2017 项目文件 lindexi 2018-08-10 19:16:53 +0 ...
- laravel .env 文件的使用
转载地址 http://www.cnblogs.com/Eden-cola/p/DotEnv-in-lumen.html umen 是 laravel 的衍生品,核心功能的使用和 laravel 都 ...
- Python--day21--包
包: 包是一种通过使用‘.模块名’来组织python模块名称空间的方式. 1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高 ...
- [转]如何让多个不同类型的后端网站用一个nginx进行反向代理实际场景分析
前段时间公司根据要求需要将聚石塔上服务器从杭州整体迁移到张家口,刚好趁这次机会将这些乱七八糟的服务器做一次梳理和整合,断断续续一个月迁移完 成大概优化掉了1/3的机器,完成之后遇到了一些问题,比如曾今 ...
- 【codeforces 764C】Timofey and a tree
time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...
- 如何在 centos 7.3 上安装 caffe 深度学习工具
有好多朋友在安装 caffe 时遇到不少问题.(看文章的朋友希望关心一下我的创业项目趣智思成) 今天测试并整理一下安装过程.我是在阿里云上测试,选择centos 7.3 镜像. 先安装 epel 源 ...
- 使用vuex来管理数据
最近一直工作比较忙,博客已经鸽了好久了,趁着今天是周末,写点东西吧 使用vuex来管理数据 最近一直在用vue做项目,但是却从来没真正去用过vuex,因为一直感觉很复杂,其实真正去研究一下啊,就会发现 ...
- linux获知当前时间
内核代码能一直获取一个当前时间的表示, 通过查看 jifies 的值. 常常地, 这个值只代 表从最后一次启动以来的时间, 这个事实对驱动来说无关, 因为它的生命周期受限于系统 的 uptime. 如 ...