相关文章推荐
风流的板栗  ·  香港閱讀城·  1 年前    · 
考研的台灯  ·  OH 3.3.0 Frontail not ...·  1 年前    · 
知识渊博的斑马  ·  Excel ...·  2 年前    · 
自信的胡萝卜  ·  Nosql-neo4j-Cypher 语句 ...·  2 年前    · 
1 类加载器

在类加载器家族中存在权力等级制度:

1.1 ​ ​Bootstrap​

由C/C++实现,启动类加载器,属最高层,JVM启动时创建,通常由与os相关的本地代码实现,是最根基的类加载器。

没有对应的Java对象,因此在Java中只能用null来指代。

JDK8 时


需要注意的是 ​,Bootstrap ClassLoader智慧加载特定名称的类库,比如rt.jar.这意味我们自定义的jar扔到​ ​<JAVA_HOME>\jre\lib​ ​也不会被加载.


负责将:


  • ​<JAVA_ HOME>/jre/lib​ ​​ 最新版JDK17下的JVM类加载器原理详解_jar
  • ​-Xbootclasspath​ ​参数指定的路径中的

且是虚拟机识别的类库加载到内存中(按名字识别,如rt.jar,不能识别的文件不予装载),如:


  • Object
  • System
  • String
  • Java运行时的rt.jar等jar包
  • 系统属性sun.boot.class.path指定的目录中特定名称的jar包

在JVM启动时,通过​ Bootstrap ClassLoader ​加载​ ​rt.jar​ ​,并初始化​ ​sun.misc.Launcher​ ​从而创建​ Extension ClassLoader ​和​ Application ClassLoader ​的实例。

查看Bootstrap ClassLoader到底初始化了那些类库:

最新版JDK17下的JVM类加载器原理详解_java_02

最新版JDK17下的JVM类加载器原理详解_jar_03

JDK9 后

负责加载启动时的基础模块类,比如:


  • java.base
  • java.management
  • java.xml

除了启动类加载器之外,其他的类加载器都是java.lang.ClassLoader的子类,因此有对应的Java对象。这些类加载器需要先由另一个类加载器,比如说启动类加载器,加载至Java虚拟机中,方能执行类加载。

1.2 平台类加载器(Platform ClassLoader)

负责加载相对次要、但又通用的类。

1.2.1 JDK8时为​ ​Extension ClassLoader​

只有一个实例,由​ sun.misc.Launcher$ExtClassLoader ​实现:

负责加载一些扩展的系统类,比如XML、加密、压缩相关的功能类等:


  • ​<JAVA_HOME>\lib\ext​ 最新版JDK17下的JVM类加载器原理详解_JVM_04
  • 或​ ​java.ext.dirs​ ​系统变量指定的路径中的所有类库

JDK9时替换为平台类加载器

加载一些平台相关模块,如​ ​java.scripting​ ​​、​ ​java.compiler*​ ​​、 ​ ​java.corba*​ ​。

为何JDK9时废除替换?

JDK8主要加载 jre lib 的ext,扩展 jar 包时使用,这样操作并不推荐,所以废除。

而 JDK9 有了模块化,更无需这种扩展加载器。

1.3 应用类加载器(Application ClassLoader)

只有一个实例,由​ ​sun.misc.Launcher$AppClassLoader​ ​实现。

JDK8时

负责加载如下所有类库:


  • 环境变量ClassPath
  • 或系统属性java.class.path指定目录
  • 或虚拟机参数-cp/-classpath

若应用程序中没有定义自己的加载器,则该加载器就是默认的类加载器。

该加载器可通过​ java.lang.ClassLoader.getSystemClassLoader ​获取。

JDK9后

应用程序类加载器,用于加载应用级别的模块,如:


  • jdk.compiler
  • jdk.jartool
  • jdk.jshell
    最新版JDK17下的JVM类加载器原理详解_类加载器_05
  • classpath路径中的所有类库
    第二、三层类加载器为Java语言实现,用户也可以

1.4 自定义类加载器

用户自定义的加载器,是​ ​java.lang.ClassLoader​ ​的子类,用户可以定制类的加载方式;只不过自定义类加载器其加载的顺序是在所有系统类加载器的最后。

1.5 Thread Context ClassLoader

每个线程都有一个类加载器(jdk 1.2后引入),称之为Thread Context ClassLoader,如果线程创建时没有设置,则默认从父线程中继承一个,如果在应用全局内都没有设置,则所有Thread Context ClassLoader为Application ClassLoader.可通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,通过Thread.currentThread().getContextClassLoader()来获取.

线程上下文加载器有什么用?

该类加载器容许父类加载器通过子类加载器加载所需要的类库,也就是打破了我们下文所说的双亲委派模型。

这有什么好处呢?

利用线程上下文加载器,我们能够实现所有的代码热替换、热部署。

2 验证类加载器

2.1 查看本地类加载器

最新版JDK17下的JVM类加载器原理详解_JVM_06

在JDK8环境中,执行结果如下

最新版JDK17下的JVM类加载器原理详解_JVM_07

AppClassLoader的Parent为Bootstrap,它是通过C/C++实现的,并不存在于JVM体系内,所以输出为 null。

类加载器的特点

类加载器无需等到某类"首次主动使用”时才加载它,JVM规范允许类加载器在预料到某个类将要被使用的时候就预先加载它。

Java程序不能直接引用启动类加载器,直接设置classLoader为null,默认就使用启动类加载器。

若在加载时,​ ​.class​ ​文件缺失,会在该类首次主动使用时通知LinkageError错误,若一直没有被使用,就不会报错。

若未指定父加载器,默认就是启动加载器。

每个类加载器都有自己的命名空间,命名空间由该加载器及其所有父加载器所加载的类构成。不同命名空间,可出现类的全路径名相同情况。

运行时包由同一个类加载器的类构成,决定两个类是否属于同一个运行时包:


  • 不仅要看全路径名是否一样
  • 还要看定义类加载器是否相同

只有属同一运行时包的类才能实现相互包内可见。

最新版JDK17下的JVM类加载器原理详解_后端_08

低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类。

若低层次的类加载器想加载一个未知类,要向上逐级询问:“请问,这个类已经加载了吗”?被询问的高层次类加载器会自省:


  • 我是否已加载过此类?
  • 若没有,我是否可以加载此类?

只有当所有高层次类加载器在两个问题的答案均为“否”时,才可以让当前类加载器加载这个未知类。

左侧绿色箭头向上逐级询问是否已加载此类,直至​ ​Bootstrap ClassLoader​ ​,然后向下逐级尝试是否能够加载此类,如果都加载不了,则通知发起加载请求的当前类加载器,准予加载。

在右侧的三个小标签里,列举了此层类加载器主要加载的代表性类库,事实上不止于此。

通过如下代码可以查看Bootstrap所有已加载类库

最新版JDK17下的JVM类加载器原理详解_类加载器_09

执行结果

最新版JDK17下的JVM类加载器原理详解_JVM_10

Bootstrap加载的路径可以追加,不建议修改或删除原有加载路径

在JVM中增加如下启动参数,则能通过​ ​Class.forName​ ​正常读取到指定类,说明此参数可以增加Bootstrap的类加载路径:

-Xbootclasspath/a:/Users/sss/book/ easyCoding/byJdk11/src

如果想在启动时观察加载了哪个jar包中的哪个类,可以增加

-XX:+TraceClassLoading

此参数在解决类冲突时非常实用,毕竟不同的JVM环境对于加载类的顺序并非是一致的

有时想观察特定类的加载上下文,由于加载的类数量众多,调试时很难捕捉到指定类的加载过程,这时可以使用条件断点功能

比如,想查看HashMap的加载过程,在loadClass处打个断点,并且在condition框内输入如图

最新版JDK17下的JVM类加载器原理详解_类加载器_11

JVM如何确立每个类在JVM的唯一性

类的全限定名 + 加载这个类的类加载器的ID。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。在大型应用中,我们往往借助这一特性,来运行同一个类的不同版本。

在学习了类加载器的实现机制后,知道双亲委派模型并非强制模型,用户可以自定义类加载器,在什么情况下需要自定义类加载器呢?


  • 隔离加载类
    在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境
    比如,阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包
  • 修改类加载方式
    类的加载模型并非强制,除Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载
  • 扩展加载源
    比如从数据库、网络,甚至是电视机机顶盒进行加载
  • 防止源码泄露
    Java代码容易被编译和篡改,可以进行编译加密。那么类加载器也需要自定义,还原加密的字节码。

实现自定义类加载器的步骤


  • 继承ClassLoader
  • 重写findClass()方法
  • 调用defineClass()方法

一个简单的类加载器实现的示例代码如下

最新版JDK17下的JVM类加载器原理详解_类加载器_12 由于中间件一般都有自己的依赖jar包,在同一个工程内引用多个框架时,往往被迫进行类的仲裁。按某种规则jar包的版本被统一指定, 导致某些类存在包路径、类名相同的情况,就会引起类冲突,导致应用程序出现异常。

主流的容器类框架都会自定义类加载器,实现不同中间件之间的类隔离,有效避免了类冲突。



Python数据清洗过程 python数据清洗方法

python数据清洗学习笔记–数据预处理 文章目录python数据清洗学习笔记--数据预处理1、重复值处理2、缺失值处理3、异常值处理4、数据离散化处理4-1、等宽分箱4-2、等频分箱 1、重复值处理• 数据清洗一般先从重复值和缺失值开始处理• 重复值一般采取删除法来处理• 但有些重复值不能删除,例如订单明细数据或交易明细数据等df[df.duplicated()] np.sum(df.dupli