文章目录

  • Java 执行代码的大致流程
  • 类加载loadClass的步骤
  • 类加载器和双亲委派机制
  • sun.misc.Launcher源码解析
    • Launcher实例化
    • Launcher 构造函数
  • 双亲委派机制 源码解析
    • 双亲委派过程
    • 源码解析 ClassLoader#loadClass
    • 双亲委派机制的优点
  • 全盘负责委托机制


Java 执行代码的大致流程

我们先回顾下Java 执行代码的大致流程

假设要执行A类的main方法

  1. 启动虚拟机 (C++负责创建) 【windows : bin/java.exe调用 jvm.dll Linux : java 调用 libjvm.so 】
  2. 创建一个引导类加载器实例 (C++实现)
  3. C++ 调用Java代码,创建JVM启动器,实例sun.misc.Launcher 【这货由引导加载器负责加载创建其他类加载器】

  1. sun.misc.Launcher.getLauncher() 获取运行类自己的加载器ClassLoader --> 是AppClassLoader , 通过上图源码可知

  2. 获取到ClassLoader后调用loadClass(“A”)方法加载运行的类A

  3. 加载完成执行A类的main方法

  4. 程序运行结束

  5. JVM销毁


类加载loadClass的步骤

其中最核心的方法 loadClass ,其实现我们常说的双亲委派机制 ,我们后面展开。

我们先白话一下类加载的几个步骤

加载 ----> 验证 ----> 准备 ----> 解析 ----> 初始化 ----> 使用 ----> 卸载

谈及比较多的是前五个 ,我们来捋一捋哈 ,不要尝试死记硬背,尝试去理解它的逻辑

  1. 加载: 我们说jvm执行的java字节码,编译后在磁盘上,总得读取这个字节码文件吧 ,通过啥读 IO呗 , 所以第一步肯定是加载字节码文件
  2. 验证 : JVM总不能说读到啥就直接运行了吧,你外面有个A.class 里面是一堆JVM规范不认识的内容,也执行不了啊 。 符合JVM规范才能执行后续的步骤,所以第二步是 校验字节码文件的正确性
  3. 准备 : 给类的静态变量分配内存,并赋予默认值。 我们的类里,可能会包含一些静态变量吧 。 比如 public static final int a = 12; 得给a分配个默认值 0 ,再比如 public static User user = new User(); 给 static的变量 User分配内存,并赋默认值null (final修饰的常量,直接赋值)
  4. 解析 : 这个地方不是很好理解, 解析是什么意思呢?将符号引用替换为直接引用。 符号引用 ? 直接引用? what ? ------------- 我们的类的静态方法 比如main方法 其实在Java中有个叫法 都是叫符号 。 这个阶段就会吧 一些静态方法(符号引用,比如刚才说的main方法)替换为指向数据所存内存的指针或者句柄等(直接引用)【找到具体在内存中的位置】。 这个就是静态链接过程(在类加载期间完成)。 动态链接是在程序运行期间完成的将符号引用替换为直接引用 (比如某个普通方法的调用)
  5. 初始化: 上面的步骤完事儿以后,这一步主要是对类的静态变量初始化为指定的值,执行静态代码块。 比如刚才第二步的 public static final int a = 12; ,第二步给static变量赋了默认值,这一步就该把12赋值给它了。 还有 static的 User public static User user = new User(); 实例化User

类加载器和双亲委派机制

刚才说了类加载器中loadClass方法实现了双亲委派的机制,那我们需要先了解下有哪几种类加载器

主要有4种

  1. 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
  2. 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
  3. 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载我们应用中自己写的那些类
  4. 自定义加载器:负责加载用户自定义路径下的类包

我们来看看 几种不同的类加载器


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;}}

看注释~

总结一下几个步骤

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法 【调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类】来完成类加载。

双亲委派机制的优点

  1. 沙箱安全机制:比如我们自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
  2. 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

全盘负责委托机制

这个比较好理解

“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。

比如我们的类 A中引用了 类B,由于全盘负责委托机制 ,类B也将有加载类A的加载器来加载,除非你显示的使用另外一个ClassLoder。


JVM-白话聊一聊JVM类加载和双亲委派机制源码解析相关推荐

  1. JVM 学习四:类加载之双亲委派机制与沙箱安全机制

    1 双亲委派机制 Java 虚拟机对 Class 文件的加载采用的是按需加载的方式,也就是说:当需要使用该类时才会将它的 Class 文件加载到内存生成 Class 对象,而且加载某个类的 Class ...

  2. java类加载-ClassLoader双亲委派机制

    "类加载体系"及ClassLoader双亲委派机制.java程序中的 .java文件编译完会生成 .class文件,而 .class文件就是通过被称为类加载器的ClassLoade ...

  3. 类加载的双亲委派机制

    类加载器的双亲委派机制 ​ 除了根类加载器之外,其他的类加载器都需要有自己的父加载器.从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全.除了虚拟机自带的根类加 ...

  4. 类加载器-双亲委派机制

    上一篇:类加载器-分类 一.概述 除了根类加载器之外,其他的类加载器都需要有自己的父加载器.从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全.除了虚拟机自带的 ...

  5. JVM系列(三):双亲委派机制笔记

    今天给大家分享JVM系列之双亲委派机制相关的知识. 1.Java类加载的过程 Java类的加载过程是动态的,它不会一次性把程序所有的类全部加载后再运行,而是先保障程序运行的基础类加载到JVM虚拟机当中 ...

  6. java 委派关系_一文读懂java类加载之双亲委派机制

    一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.java中将类的加载工具抽象为类加载器,而通过加载工具加载类文件的具体方式被称为双亲委派机制. 知识点 类加载器:通过一个类全限 ...

  7. java委派_一文读懂java类加载之双亲委派机制

    作者:程序猿微录 出自:TinyRecord 一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.java中将类的加载工具抽象为类加载器,而通过加载工具加载类文件的具体方式被称为双 ...

  8. 关于类加载的双亲委派机制简单总结

    前言 我们知道,一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.而做这个工作的老哥就是类加载器.而通过类加载器,加载类文件的具体方式被称为双亲委派机制. 什么是类加载器?有哪些 ...

  9. java中的类加载与双亲委派机制

    类加载是什么 把磁盘中的java文件加载到内存中的过程叫做类加载 当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM. 有如下 User 类 package ...

最新文章

  1. Ubuntu NFS 服务器和客户端挂载详解
  2. php mysql sql mode_Mysql之SQL Mode用法详解_MySQL
  3. 设计模式原则--单一职责原则
  4. .net之 datagrid
  5. SpringBoot 集成 druid 监控数据库报错 Failed to bind properties under ‘xxxx‘ to javax.sql.DataSource 解决(含配置源码)
  6. python以运行效率高著称吗_几个提升Python运行效率的方法之间的对比
  7. re库、正则表达式基本使用
  8. InstallShield 杀掉进程
  9. [POJ3274 Gold Balanced Lineup]
  10. 财务人员必备的5个Excel技能,学会工资高出同事3倍!
  11. 动态加载下拉框select options 如何设置默认选中
  12. MULTISIM安装下载
  13. 推荐 7 个 Python 入门视频教程
  14. 图片质量与ISO 光圈 快门 测光 曝光与曝光补偿 焦距和焦距转换系数 景深与光圈优先 白平衡与RAW
  15. 揭开Angular 7的神秘面纱
  16. 航天晨光:永中DCS与原有OA系统整合,文件阅览效率大幅提升!
  17. matlab模拟塞曼图谱,基于MATLAB的塞曼效应数值模拟
  18. Laravel Collect集合用pluck取多维数组中某个字段值
  19. JS完成注册页面的省市联动(JS内置对象全局函数,select标签操作)
  20. 价值4500的国际版多语言点赞抖音分享点赞任务平台源码(十二种语言)

热门文章

  1. C++构造函数初始化列表
  2. tf.nn.conv2d 与tf.layers.conv2d的区别
  3. python生产脚本_生产级部署 Python 脚本,日志收集、崩溃自启,一键搞定
  4. 13. Leetcode 349. 两个数组的交集 (数组-分离双指针)
  5. 机器学习笔记:激活函数
  6. MySQL快速查询的12条建议,让你不止会用select *
  7. 来个邪恶假说,假如有人把支付宝所有存储服务器炸了,我们在里边的钱是不是都丢了?
  8. 算法效果AB测试中的PV-UV不对称性
  9. 小白入门PyTorch | 第一篇:什么是PyTorch?
  10. 机器学习第11天:朴素贝叶斯模型 - 垃圾短信识别