jvm双亲委派机制详解
双亲委派机制
记录一下JVM的双亲委派机制学习记录。
类加载器种类
当我们运行某一个java类的main方法时,首先需要由java虚拟机的类加载器将我们要执行的main方法所在的class文件加载到jvm中,这里提到的类加载器大概有4种:
引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
自定义加载器:负责加载用户自定义路径下的类包。
每个类加载器加载的包路径都是不同的,有各自的职责。通过一下示例,可以看出每个类加载器加载的路径:
public class TestJDKClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
// 运行结果:
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@330bedb4
the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc
bootstrapLoader加载以下文件:
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/resources.jar
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/rt.jar
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/sunrsasign.jar
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/jsse.jar
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/jce.jar
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/charsets.jar
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/jfr.jar
file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/classes
extClassloader加载以下文件:
D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
appClassLoader加载以下文件:
D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\charsets.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\deploy.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\access-bridge-64.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\cldrdata.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\dnsns.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\jaccess.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\jfxrt.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\localedata.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\nashorn.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunec.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunjce_provider.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunmscapi.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunpkcs11.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\zipfs.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\javaws.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jce.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jfr.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jfxswt.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jsse.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\management-agent.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\plugin.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\resources.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\rt.jar;D:\Files\learn\tuling\jvm-demo\target\classes;D:\ProgramFiles\ideaIU-2021.3.3.win\lib\idea_rt.jar
Process finished with exit code 0
appClassLoader虽然打印的内容虽然很多,但它只需要加载target目录下的文件。
双亲委派机制
虽然基本只有4种类加载器,但这4种类加载器之间是存在一定的关联关系的。如下图:
加载某个类时会先委托给父加载器寻找目标类,找不到再委托上层父类加载器加载,所有的父类加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中寻找并载入目标类。
比如上面的TestJDKClassLoader,首先会委托应用程序类加载,应用程序类加载器则委托扩展类加载器加载,扩展类加载器则委托引导类加载器加载,引导类加载器在它的类加载路径下找不到TestJDKClassLoader.class文件,则向下委托扩展类加载器加载,扩展类加载器加载不到则委托应用程序类加载器自己加载,于是应用程序类加载器在target目录下找到并载入了TestJDKClassLoader.class文件。
我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:
我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:
我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:
首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接
返回。如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加
载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加
载。如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的
findClass方法来完成类加载。
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;
}
}
为什么要设计双亲委派机制?
沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心
API库被随意篡改
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一
次,保证被加载类的唯一性
例子:比如我们自己新建了一个java.lang.String类,我们看能不能加载成功。
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("============自己的类加载器====");
}
}
// 执行结果
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
自定义类加载器
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String[] args) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
//D盘创建 test/com/hyz/jvm 几级目录,将User类的复制类User1.class丢入该目录
Class clazz = classLoader.loadClass("com.hyz.jvm.User1");
// Class clazz = classLoader.loadClass("java.lang.String");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("hello", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
打破双亲委派机制
假如我们的target下有一个User类,但是我们的程序代码中需要去读取D:/test/com/hyz/jvm/User.class类,根据双亲委派机制,肯定是会加载到target下的User类的,要如何才能加载到D:/test下的User类呢?
那意味着我们需要去打破双亲委派机制。看AppClassLoader的类加载逻辑,主要逻辑在父类ClassLoader.loadClass()方法中,我们只需要在自定义的类加载器中重写该方法即可。主要修改逻辑:如果类型是com.hyz.jvm开头的类,则从自定义类加载器中去读取,否则委托给上层类加载器加载。
/**
* 32 * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
* 33 * @param name
* 34 * @param resolve
* 35 * @return
* 36 * @throws ClassNotFoundException
* 37
*/
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) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
if (!name.startsWith("com.hyz.jvm")) {
c = this.getParent().loadClass(name);
} else {
c = findClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
应用到打破双亲委派机制的实际应用场景是在Tomcat加载war包。比如war1用的是spring4版本,war2用的是spring5版本,那就意味着加载着2个war包不能用同一个类加载器实例,需要各自指定一个自定义的类加载器实例,各自去加载所需的spring版本库文件。
总结:双亲委派机制保证了核心类的安全,确保不会被修改,也保证了不会加载到重复的字节码文件。
jvm双亲委派机制详解的更多相关文章
- JVM的类加载过程以及双亲委派模型详解
JVM的类加载过程以及双亲委派模型详解 这篇文章主要介绍了JVM的类加载过程以及双亲委派模型详解,类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象 ...
- [转帖]说一说JVM双亲委派机制与Tomcat
说一说JVM双亲委派机制与Tomcat https://www.cnblogs.com/dengchengchao/p/11844022.html 讲个故事: 以前,爱捣鼓的小明突然灵机一动,写出了下 ...
- 说一说JVM双亲委派机制与Tomcat
双亲委派模型与JVM 类加载 讲个故事: 以前,爱捣鼓的小明突然灵机一动,写出了下面的代码 package java.lang; public class String { //...复制真正Stri ...
- JVM——三个ClassLoader详解
类装载工作由ClassLoader及其子类负责,ClassLoader是一个重要的Java执行时系统组件,它负责在运行时查找和装入Class字节码文件.JVM在运行时会产生三个ClassLoader: ...
- JVM类加载机制详解(二)类加载器与双亲委派模型
在上一篇JVM类加载机制详解(一)JVM类加载过程中说到,类加载机制的第一个阶段加载做的工作有: 1.通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件).而获取的方式,可 ...
- JVM类加载机制详解
引言 如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 在加载阶段,虚拟机需要完成以下三件事情: 1)通过一个类的全限定名来获取定义此 ...
- JVM加载类的过程,双亲委派机制中的方法
JVM加载类的过程: 1)JVM中类的整个生命周期: 加载=>验证=>准备=>解析=>初始化=>使用=>卸载 1.1.加载 类的加载阶段,主要是获取定义此类的二进 ...
- JVM的垃圾回收机制详解和调优
JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...
- 深入JVM系列(三)之类加载、类加载器、双亲委派机制与常见问题
一.概述 定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型.类加载和连接的过程都是在运行期间完成的. 二. 类的 ...
随机推荐
- 【NOI P模拟赛】混凝土粉末(整体二分)
题面 样例输入 5 8 1 1 4 2 2 3 1 2 3 3 1 2 5 1 2 3 3 2 5 2 2 1 2 2 1 3 样例输出 1 0 4 0 1 0 样例解释 题解 比这道题简单了不知多少 ...
- gcd(数论)
题目描述 给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对? 输入 一个整数 1<=N<=1000000 输出 一个整数 样例输入 4 样例输出 ...
- 「学习笔记」单调队列优化dp
目录 算法 例题 最大子段和 题意 思路 代码 修剪草坪 题意 思路 代码 瑰丽华尔兹 题意 思路 代码 股票交易 题意 思路 代码 算法 使用单调队列优化dp 废话 对与一些dp的转移方程,我们可以 ...
- 【三维地图】开发攻略 —— 详解“GeoJSON”技术和应用场景
GeoJSON ,一个用于存储地理信息的数据格式.GoeJSON对象可以表示几何.特征或特征集合,支持:点.线.面.多点.多线.多面和几何集合.在基于平面地图,三维地图中都需要用到的一种数据类型. 由 ...
- OpenJudge 1.5.28 分离整数的各个数位
28:分离整数的各个数位 总时间限制: 1000ms 内存限制: 65536kB 描述 给定一个整数,要求从个位开始分离出它的每一位数字. 输入 输入一个整数,整数在1到100000000之间. 输出 ...
- yield功能分析
下面是示例 输出结果: starting... 4 ******************** res: None 4
- git reset总结
git reset git 的重置操作 有三种模式:hard.mixed(默认).soft 1. hard 用法 hard会重置stage区和工作区,和移动代码库上HEAD 和branch的指针所指向 ...
- Skype for Business server 数据库安装
之前安装了SFB 2015标准版,但是没有安装归档据库,现在打算重新安装.环境中安装的是默认自带的SQL EXPRESS. 继续安装向导,安装SQL数据库.但是在最后的时候遇到了问题. 安装向导报错 ...
- G&GH02 储存库创建/同步
注意事项与声明 平台: Windows 10 作者: JamesNULLiu 邮箱: jamesnulliu@outlook.com 博客: https://www.cnblogs.com/james ...
- 5.Ceph 基础篇 - 认证
文章转载自:https://mp.weixin.qq.com/s?__biz=MzI1MDgwNzQ1MQ==&mid=2247485272&idx=1&sn=4b27c357 ...