JVM-白话聊一聊JVM类加载和双亲委派机制源码解析
文章目录
- Java 执行代码的大致流程
- 类加载loadClass的步骤
- 类加载器和双亲委派机制
- sun.misc.Launcher源码解析
- Launcher实例化
- Launcher 构造函数
- 双亲委派机制 源码解析
- 双亲委派过程
- 源码解析 ClassLoader#loadClass
- 双亲委派机制的优点
- 全盘负责委托机制
Java 执行代码的大致流程
我们先回顾下Java 执行代码的大致流程
假设要执行A类的main方法
- 启动虚拟机 (C++负责创建) 【windows : bin/java.exe调用 jvm.dll Linux : java 调用 libjvm.so 】
- 创建一个引导类加载器实例 (C++实现)
- C++ 调用Java代码,创建JVM启动器,实例sun.misc.Launcher 【这货由引导加载器负责加载创建其他类加载器】
sun.misc.Launcher.getLauncher() 获取运行类自己的加载器ClassLoader --> 是AppClassLoader , 通过上图源码可知
获取到ClassLoader后调用loadClass(“A”)方法加载运行的类A
加载完成执行A类的main方法
程序运行结束
JVM销毁
类加载loadClass的步骤
其中最核心的方法 loadClass ,其实现我们常说的双亲委派机制 ,我们后面展开。
我们先白话一下类加载的几个步骤
加载 ----> 验证 ----> 准备 ----> 解析 ----> 初始化 ----> 使用 ----> 卸载
谈及比较多的是前五个 ,我们来捋一捋哈 ,不要尝试死记硬背,尝试去理解它的逻辑
- 加载: 我们说jvm执行的java字节码,编译后在磁盘上,总得读取这个字节码文件吧 ,通过啥读 IO呗 , 所以第一步肯定是加载字节码文件
- 验证 : JVM总不能说读到啥就直接运行了吧,你外面有个A.class 里面是一堆JVM规范不认识的内容,也执行不了啊 。 符合JVM规范才能执行后续的步骤,所以第二步是 校验字节码文件的正确性
- 准备 : 给类的静态变量分配内存,并赋予默认值。 我们的类里,可能会包含一些静态变量吧 。 比如
public static final int a = 12;
得给a分配个默认值 0 ,再比如public static User user = new User();
给 static的变量 User分配内存,并赋默认值null (final修饰的常量,直接赋值) - 解析 : 这个地方不是很好理解, 解析是什么意思呢?将符号引用替换为直接引用。 符号引用 ? 直接引用? what ? ------------- 我们的类的静态方法 比如main方法 其实在Java中有个叫法 都是叫符号 。 这个阶段就会吧 一些静态方法(符号引用,比如刚才说的main方法)替换为指向数据所存内存的指针或者句柄等(直接引用)【找到具体在内存中的位置】。 这个就是静态链接过程(在类加载期间完成)。 动态链接是在程序运行期间完成的将符号引用替换为直接引用 (比如某个普通方法的调用)
- 初始化: 上面的步骤完事儿以后,这一步主要是对类的静态变量初始化为指定的值,执行静态代码块。 比如刚才第二步的
public static final int a = 12;
,第二步给static变量赋了默认值,这一步就该把12赋值给它了。 还有 static的 Userpublic static User user = new User();
实例化User
类加载器和双亲委派机制
刚才说了类加载器中loadClass方法实现了双亲委派的机制,那我们需要先了解下有哪几种类加载器
主要有4种
- 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
- 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
- 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载我们应用中自己写的那些类
- 自定义加载器:负责加载用户自定义路径下的类包
我们来看看 几种不同的类加载器
public class ClassLoadTest {public static void main(String[] args) {// 核心rt.jar中的类加载器 是C++加载的,因此这里为null System.out.println(String.class.getClassLoader());// 扩展包的加载器 ExtClassLoaderSystem.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader());// 应用加载器 AppClassLoaderSystem.out.println(ClassLoadTest.class.getClassLoader());System.out.println("");// 获取系统ClassLoaderClassLoader appClassLoader = ClassLoader.getSystemClassLoader();// appClassLoader的父加载器ClassLoader extClassLoader = appClassLoader.getParent();// extClassLoader的父加载器ClassLoader boostrapClassLoader = extClassLoader.getParent();System.out.println("the bootstrapLoader : " + boostrapClassLoader);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@29453f44
sun.misc.Launcher$AppClassLoader@18b4aac2the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@29453f44
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2==============bootstrapLoader加载的文件====================
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/resources.jar
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/rt.jar
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/sunrsasign.jar
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/jsse.jar
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/jce.jar
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/charsets.jar
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/jfr.jar
file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/classes==============extClassloader加载的文件====================
E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext==============appClassLoader 加载的文件====================
E:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;D:\IdeaProjects\GOF23\target\classes;C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar
看看appClassLoader 咋加载这么多? 其实它并没有加载这么多,除了 D:\IdeaProjects\GOF23\target\classes; 是它加载的,剩下的都是他的父加载器给他干的。
sun.misc.Launcher源码解析
JVM启动时,C++会实例化JVM启动器实例sun.misc.Launcher ,所以很有必要研究一下Launcher的源码 。
Launcher实例化
private static Launcher launcher = new Launcher();
采用了 饿汉模式 静态域的方式 实现了单例模式 ,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。
Launcher 构造函数
实例化,调用构造函数,我们看下它的构造函数干了啥?
public Launcher() {Launcher.ExtClassLoader var1;try {//构造扩展类加载器,在构造的过程中将其父加载器设置为nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try { //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,//Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);...............}}
Launcher构造方法内部, 创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。
双亲委派机制 源码解析
双亲委派过程
通俗的说: 当我们需要加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
举个例子,我们有个类A.class ,最先会找应用程序类加载器AppClassLoader 加载,AppClassLoader 会先委托扩展类加载器ExtClassLoader加载,扩展类加载器再委托引导类加载器BootClassLoader,顶层引导类加载器BootClassLoader在自己的类加载路径里 没找到A类,则向下退回加载A类的请求,扩展类加载器ExtClassLoader收到回复就自己加载,在自己的类加载路径里找了半天也没找到A类,又向下退回A类的加载请求给应用程序类加载器AppClassLoader ,应用程序类加载器 在自己的类加载路径里找A类,结果找到了就自己加载了。。
源码解析 ClassLoader#loadClass
loadClass实现了双亲委派的功能,我们有必要好好的研究一下
既然都是委托向上查找,那我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类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) {}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}
看注释~
总结一下几个步骤
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法 【调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类】来完成类加载。
双亲委派机制的优点
- 沙箱安全机制:比如我们自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
全盘负责委托机制
这个比较好理解
“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。
比如我们的类 A中引用了 类B,由于全盘负责委托机制 ,类B也将有加载类A的加载器来加载,除非你显示的使用另外一个ClassLoder。
JVM-白话聊一聊JVM类加载和双亲委派机制源码解析相关推荐
- JVM 学习四:类加载之双亲委派机制与沙箱安全机制
1 双亲委派机制 Java 虚拟机对 Class 文件的加载采用的是按需加载的方式,也就是说:当需要使用该类时才会将它的 Class 文件加载到内存生成 Class 对象,而且加载某个类的 Class ...
- java类加载-ClassLoader双亲委派机制
"类加载体系"及ClassLoader双亲委派机制.java程序中的 .java文件编译完会生成 .class文件,而 .class文件就是通过被称为类加载器的ClassLoade ...
- 类加载的双亲委派机制
类加载器的双亲委派机制 除了根类加载器之外,其他的类加载器都需要有自己的父加载器.从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全.除了虚拟机自带的根类加 ...
- 类加载器-双亲委派机制
上一篇:类加载器-分类 一.概述 除了根类加载器之外,其他的类加载器都需要有自己的父加载器.从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全.除了虚拟机自带的 ...
- JVM系列(三):双亲委派机制笔记
今天给大家分享JVM系列之双亲委派机制相关的知识. 1.Java类加载的过程 Java类的加载过程是动态的,它不会一次性把程序所有的类全部加载后再运行,而是先保障程序运行的基础类加载到JVM虚拟机当中 ...
- java 委派关系_一文读懂java类加载之双亲委派机制
一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.java中将类的加载工具抽象为类加载器,而通过加载工具加载类文件的具体方式被称为双亲委派机制. 知识点 类加载器:通过一个类全限 ...
- java委派_一文读懂java类加载之双亲委派机制
作者:程序猿微录 出自:TinyRecord 一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.java中将类的加载工具抽象为类加载器,而通过加载工具加载类文件的具体方式被称为双 ...
- 关于类加载的双亲委派机制简单总结
前言 我们知道,一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.而做这个工作的老哥就是类加载器.而通过类加载器,加载类文件的具体方式被称为双亲委派机制. 什么是类加载器?有哪些 ...
- java中的类加载与双亲委派机制
类加载是什么 把磁盘中的java文件加载到内存中的过程叫做类加载 当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM. 有如下 User 类 package ...
最新文章
- Ubuntu NFS 服务器和客户端挂载详解
- php mysql sql mode_Mysql之SQL Mode用法详解_MySQL
- 设计模式原则--单一职责原则
- .net之 datagrid
- SpringBoot 集成 druid 监控数据库报错 Failed to bind properties under ‘xxxx‘ to javax.sql.DataSource 解决(含配置源码)
- python以运行效率高著称吗_几个提升Python运行效率的方法之间的对比
- re库、正则表达式基本使用
- InstallShield 杀掉进程
- [POJ3274 Gold Balanced Lineup]
- 财务人员必备的5个Excel技能,学会工资高出同事3倍!
- 动态加载下拉框select options 如何设置默认选中
- MULTISIM安装下载
- 推荐 7 个 Python 入门视频教程
- 图片质量与ISO 光圈 快门 测光 曝光与曝光补偿 焦距和焦距转换系数 景深与光圈优先 白平衡与RAW
- 揭开Angular 7的神秘面纱
- 航天晨光:永中DCS与原有OA系统整合,文件阅览效率大幅提升!
- matlab模拟塞曼图谱,基于MATLAB的塞曼效应数值模拟
- Laravel Collect集合用pluck取多维数组中某个字段值
- JS完成注册页面的省市联动(JS内置对象全局函数,select标签操作)
- 价值4500的国际版多语言点赞抖音分享点赞任务平台源码(十二种语言)
热门文章
- C++构造函数初始化列表
- tf.nn.conv2d 与tf.layers.conv2d的区别
- python生产脚本_生产级部署 Python 脚本,日志收集、崩溃自启,一键搞定
- 13. Leetcode 349. 两个数组的交集 (数组-分离双指针)
- 机器学习笔记:激活函数
- MySQL快速查询的12条建议,让你不止会用select *
- 来个邪恶假说,假如有人把支付宝所有存储服务器炸了,我们在里边的钱是不是都丢了?
- 算法效果AB测试中的PV-UV不对称性
- 小白入门PyTorch | 第一篇:什么是PyTorch?
- 机器学习第11天:朴素贝叶斯模型 - 垃圾短信识别