自学JVM系列:类加载和双亲委派机制

自学JVM系列:类加载和双亲委派机制,第1张

文章目录
  • JVM创建、类加载、销毁宏观流程图
  • classLoader.loadClass()类加载的具体过程
    • 符号引用和直接引用个人理解
  • 类加载器
  • 双亲委派机制
    • 前提
    • 双亲委派机制大白话解释
    • 源码
    • 为什么要用双亲委派机制
      • 猜测一(最大的优点):沙箱安全机制
      • 猜测二
  • 全盘负责委托机制(个人觉得不太重要)
  • 自定义类加载器示例
  • 打破双亲委派机制

JVM创建、类加载、销毁宏观流程图


上图是jvm创建->类加载到JVM->JVM销毁的宏观流程图
左侧底层是C++的HotSpot实现的
右侧是java相关的源码,参考Launcher类和classLoader类

classLoader.loadClass()类加载的具体过程

其中loadClass() 的类加载过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
1、加载:首先将.class字节码文件从硬盘上加载到jvm内存区域上。在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2、验证:校验字节码文件的正确性。以”cafe babe”为开头的字节码才能够被jvm接受。
3、准备:给类的 静态变量 分配内存,并赋予默认值,比如对象赋null,整型赋0等。
4、解析:将 符号引用 替换为 直接引用,该阶段会把一些静态方法(符号引用,比如
main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过
程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。
**5、初始化:**对类的静态变量初始化为指定的值,执行静态代码块。

符号引用和直接引用个人理解

1、看一下class字节码文件里的内容
命令 javap -v Test.class (这里的Test.class是build后产生的一个字节码文件)

2、constant pool常量池里的数据都是一些 以’#'开头的地址,后面是他的值
再看看字节码文件里的逻辑

跟着这个逻辑翻译一下,就可以看到我们写的java代码的逻辑,也就是说我们写的java代码都是符号引用(修饰符、类名、方法名这些在字节码里叫做符号),将这些符号替换为 指向常量池里的地址的指针 的 *** 作,就可以理解为是符号引用变为直接引用的一个过程。

类加载器

上面的类加载过程主要是通过类加载器来实现的,Java里有4种类加载器:
1、引导类加载器(null):负责加载支jvm运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等。
为什么是null呢,因为引导类加载器是c++实现的,java无法获取,引导类加载器会调用Launcher.getLauncher()
2、扩展类加载器(ExtClassLoader):负责加载支撑jvm运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包。
3、应用程序类加载器(AppClassLoader):负责加载ClassPath路径下的类包,其实就是加载你自己写的那些类。
4、自定义加载器:负责加载用户自定义路径下的类包。

双亲委派机制

从上到下,由父到子

前提

jvm 默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。这个可以参考Launcher类的初始化方法Launcher()来证实,返回的是AppClassLoader。

双亲委派机制大白话解释

加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
比如你写了个Test类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天没找到Test类,则向下退回加载Test类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Test类,又向下退回Test类的加载请求给应用程序类加载器, 应用程序类加载器于是在自己的类加载路径里找Test类,结果找到了就自己加载了。。。

源码


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

为什么要用双亲委派机制 猜测一(最大的优点):沙箱安全机制

自己写的类似核心类的一摸一样的核心类不会被加载,这样可以方式核心类被随意篡改
比如,你自己写了一个java.lang.String类,真正加载的时候,其实加载的是rt.jar下的核心String类,你自己写的压根没起作用

猜测二

项目里的很多类都是自己写的应用类,这些类第一次被加载时,会走一遍双亲委派的流程(从子到父,再从父到子),当第二次使用时,就不会再加载了,这样其实是一种优化,也保证了类只加载一次的唯一性。

全盘负责委托机制(个人觉得不太重要)

是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,否则该类所依赖及引用的类也由这个ClassLoder载入。
比如 A类里 new了个B类,如果A使用AppClassLoader加载的,则B类也用AppClassLoader加载,个人认为,就算你A类用到了核心的类,利用双亲委派机制,也会委派到到引导类加载器里进行加载。

自定义类加载器示例

自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。

打破双亲委派机制

重写loadClass()方法,删除掉里面的双亲委派逻辑的代码

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/719574.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存