当前位置:主页 > 查看内容

类加载器原理

发布时间:2021-06-07 00:00| 位朋友查看

简介:一、类加载 一 TraceClassLoading TraceClassLoading参数可以显示JVM从进程开始到运行结束的时候 所有ClassLoad的相关信息。在JDK8上 用“-XX: TraceClassLoading”就可以显示 在JDK11上的话 要加上 “-Xlog: class load info”。 下方是JDK11上打出来的一些……

一、类加载

一 TraceClassLoading

TraceClassLoading参数可以显示JVM从进程开始到运行结束的时候 所有ClassLoad的相关信息。在JDK8上 用“-XX: TraceClassLoading”就可以显示 在JDK11上的话 要加上 “-Xlog: class load info”。

下方是JDK11上打出来的一些日志 可以看到时间 类 还有类从哪个模块里来 信息非常详细。

image.png

?

二 类加载与虚拟机

关于类加载部分 首先用户有Java文件 然后Java文件用Java c去编译就可以得到.class文件 接着虚拟机会加载.class文件变成虚拟机的元数据。比如在c 里边会变成Klass * Method * ConstantPool * 等 这些都是Java虚拟机里元数据的描述。

比如一个Class会变成一个Klass*的结构体 这个Class里面所有方法会变成虚拟机里面Method*的结构体 然后常量池会被包装成一个ConstantPool* 这些在虚拟机里都有相关描述。

?

三 ClassFile

image.png

上图为ClassFile的结构 它的反汇编是Java.lang.string。

如果用户想构造一个String 就必须要传给它一个字符串的自变量 自变量会被传到Value的数组里。可以看到 在JDK11当中Value是用Stable Annotation修饰的。

image.png

和上图对比 可以发现Private Final以及Byte的数组全都被很好地描述在Java p反汇编的Class文件中 Stable annotation被描述在ClassFile里。

我们来看一个例子。

image.png

rangeCheck是String里边的一个Static方法 这个方法有三个参数value、offset和count 它内部会调用一个static的方法 并且返回null。

image.png

对照上方的Java p反汇编的class文件 反汇编的文件分为三个部分 第一个部分是Code 第二个部分是LineNumberTable 以及LocalVariableTable。

Code当中iload_1 iload_2以及aload_10都是字节码 可以看到LineNumberTable里的第280行对应的0 这个0是上面Code的第0行 也就是iload_1。下面的line 281行的7对应的是aconst_null字节码。

LocalVariableTable的Start、Length对应的都是字节码的位置 后面还有名字等信息。

例如value这个变量是从第0号字节码 它的生命周期一直从0号到第9位字节码 第9位是左开右闭区间 因此不包括第9号字节码。可以看到 所有的信息都会被完整保存在ClassFile里。

image.png

可以看到 上图所示的Annotations类上面有无数的注解 例如IA、IB、IC 它们都是Annotations的定义 Annotations可以插在程序的各个地方 这张图只是为了一个直观的表示 然后来看一下Annotations是怎么样被Incode进ClassFile里面的 可以直观对比下图的变量。

image.png

?

四 ClassLoader结构

image.png

Class这些元数据在JVM当中是如何被表示的

假设有一个ClassLoader正在Loader一些类 然后把它们Load进虚拟机当中。JVM当中有一个结构体叫做SystemDictionary 它是一个Meta 会把Class的类名Meta到Class的Pointer当中 然后Pointer指向的就是Metaspace当中真正的Class结构描述。

Class当中有一些Mirror的字段 这些Mirror指向java.lang.Class。Mirror和上层的.class是一样的 是一个反射接口的作用。

可以看到 ClassLoader会索引到SystemDictionary 然后索引到Metaspace Chunk 接着索引到Heap 这几个可以互相引用。

图中Metaspace Chunk的Klass以及Heap里的java.lang.Class图形大小是不同的。因为用户自己写的Class有可能会继承自不同的父类以及不同的接口 它有可能实现了若干个父类和接口 实现接口和父类的数量有所不同 Class里的东西也是不尽相同 因此元数据的大小也是不一样的。

?

五 双亲委派机制

image.png

Java的ClassLoader有双亲委派机制 先使用双亲类加载器进行加载 当 Parent加载失败的时候 再自己加载。

Bootstrap Class Loader、Extension Class Loader和System Class Loader 即APP Class Loader 这三个Class Loader是父子的关系。如果先从APP Class Loader加载用户的命令Class 会先去Extension Class Loader加载 然后去Bootstrap Class Loader加载 如果它们都没有加载到 最后才会轮到System Class Loader加载。

所有User Defined Class Loader的Parent基本都是System Class Loader 用户可以选择自己是否要写一个新的Class Loader。

LoadClass类是ClassLoader内部一个非常通用性的类 如果要重写一个ClassLoader的话 会选择重写里面的findLoadedClass这个方法 而不会选择LoadClass。

image.png

如上图所示 首先是一个synchronized 加上get ClassLoadingLock的同步锁。它下面会先调用一个findLoadedClass 这个函数会去SystemDictionary里去找到相应的类。如果它没有 那么就会到Parent中loadClass 如果Parent里也没有 就会到findClass的方法。

?

六 破坏双亲委派机制

image.png

?? Tomcat ClassLoader 为例 它会经过以下过程

1 在本地 ResourceEntry 当中查找

2 调用 ClassLoader.findLoadedClass()

3 默认情况下调用 AppClassLoader.loadClass()

4 用自身加载

5 依旧没有加载出来的情况 最后才委派给Parent

?

?? 意义 可以实现一个 VM 进程下加载不同版本的 jar 包

?

七 ParallelCapable

从JDK1.7开始 ClassLoader引入了一个叫ParallelCapable的特性。

之前的JDK当一个ClassLoader在LoadClass的时候 它会锁ClassLoader 锁的粒度是整个ClassLoader。在1.7引入了ParallelCapable特性之后 锁的粒度变成了Class 大幅提高ClassLoader的性能。

先ClassLoader在loadClass 时同步整个 loader 对象 现在把锁变成了单个类名对应的Placeholder。如果要Load一个Class 检查类名就可以找到相应的Placeholder。

下面我们来看一下它到底是怎么实现的。

image.png

如上图所示 第一行的关键字synchronized锁住了getClassLoadingLock。这个方法会从非权限命名所对应的Object的Map里边搜索到它对应的Placeholder 也就是占位符 它只要锁住了占位符 后面的过程就全是进程安全了。

下面我们来看一下虚拟机里面的实现。

image.png

DoObject变量决定了当前的ClassLoader是否要锁住整个ClassLoader来加载一个类。如果是true 就会去锁住整个ClassLoader。如果它是false的话 它就会像刚才一样做synchronized操作 synchronized锁住的是它加载的类对应的名字所对应的Placeholder。这样的话它就把C 层锁住整个ClassLoader的代价 转移到了Java层 去锁住Class。

?

?

二、链接

?? 链接的过程如下

1 先递归地对所有父类和接口进行链接操作

2 verify 当前类

3 rewrite 当前类

n? 比如会把 java.lang.Object. init 构造函数的 _return 字节码重写为 _return_register_finalizer 字节码

image.png

n? 比如 _lookupswitch 这种不连续的 switch 跳转分支数在 BinarySwitchThreshold (default 5) 以下会被重写成_fast_linearswitch 字节码 否则会变成 _fast_binaryswitch 字节码

image.png

image.png

n? 比如 _aload_0 _getfield (integer) 的组合最终会被 rewrite 成 _fast_iaccess_0 字节码

4 对类内部的所有方法进行链接操作 使其生效 设置方法执行的入口为解释器的入口 。

?

?

三、初始化

一 初始化操作

在虚拟机规范当中 我们可以看到这样的描述

1 在_new/_getstatic/_putstatic/_invokestatic字节码时/反射/lambda解析发现callee是一个static 函数时触发

2 调用 class 的 clinit 方法

3 实例化。

image.png

我们写Java程序的时候用的Static变量 在虚拟机内部会转化成一个叫 clinit 的方法 然后实例化。如果用反射去New一个Object 或者是走New字节码的时候 都会进行初始化的操作。

上图是一个 clinit 的方法 截取的是java.lang.Object的Static块 它只有一条的代码。

?

二 编写自己的 ClassLoader

?? 方法

1 按照 ClassLoader.loadClass() 的模板来重写 不推荐

2 仅重写 findClass() 方法 拿到并解析.class 文件为一个 byte[] 数组 并调用 defineClass()方法进入VM。

?

三 Class Unloading

?? JDK8与JDK11中都有-XX: ClassUnloading (default true)

?? Class Unloading发生在当一个类不被任何引用所引用时 就可以被unload掉

1 一个类被加载的时候 会产生 ClassLoader - Class 的引用 因此 ClassLoader 自身需要先不被任何引用所引用

2 其他GC roots无对此类的引用 比如栈帧等

?

四 向JDK11迁移

?? JDK8和JDK11中JDK library中的ClassLoader有所不同

1 ExtClassLoader 更名为了PlatformClassLoader

2 PlatformClassLoader和AppClassLoader不再继承自URLClassLoader

3 如果指定了 -Djava.ext.dirs 这个变量 需要用-classpath 来加以替代

4 如果指定了-Djava.endorsed.dirs来覆盖JDK内部的API 需要删掉参数。

?

五 AppCDS (APPlication Class Data Sharing)

?? AppCDS是OpenJDK做的一个特性 它有以下特点

1 用程序加载的classes 产生 *.jsa 文件 (shared archive) 给应用的启动进行加速;

2 JDK 1.5 时为 CDS 只能用dump BootstrapClassLoader 加载的类;

3 JDK10后变为AppCDS 可以用于AppClassLoader和custom ClassLoaders。

?

?? AppCDS本质是动态分析流程 使用步骤如下

1 第一次 java -Xshare:off -XX:DumpLoadedClassList list.log app

2 第二次 java -Xshare:dump -XX:SharedClassListFile list.log XX:SharedArchiveFile dump.jsa app

3 第三次 java -Xshare:on -XX:SharedArchiveFile dump.jsa app

JDK 在build 的时候 会使用Java加上AppCDS的参数自动产生一份.jsa 文件来加速启动 放在 ${JAVA_HOME}/lib/server 下 会什么参数都不加 裸跑一个.jsa 文件 产生的文件叫classes.jsa 用户搜自己JDK11的目录都可以搜到。


本文转自网络,原文链接:https://developer.aliyun.com/article/784539
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐