类加载器原理

简介: 一、类加载二、链接三、初始化

一、类加载

(一)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

rangeCheckString里边的一个Static方法,这个方法有三个参数valueoffsetcount,它内部会调用一个static的方法,并且返回null

image.png

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

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

LocalVariableTableStartLength对应的都是字节码的位置,后面还有名字等信息。

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

image.png

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

image.png

 

(四)ClassLoader结构

image.png

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

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

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

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

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

 

(五)双亲委派机制

image.png

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

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

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

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

image.png

如上图所示,首先是一个synchronized,加上get ClassLoadingLock的同步锁。它下面会先调用一个findLoadedClass,这个函数会去SystemDictionary里去找到相应的类。如果它没有,那么就会到ParentloadClass,如果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当一个ClassLoaderLoadClass的时候,它会锁ClassLoader,锁的粒度是整个ClassLoader。在1.7引入了ParallelCapable特性之后,锁的粒度变成了Class,大幅提高ClassLoader的性能。

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

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

image.png

如上图所示,第一行的关键字synchronized锁住了getClassLoadingLock。这个方法会从非权限命名所对应的ObjectMap里边搜索到它对应的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.ObjectStatic块,它只有一条的代码。

 

(二)编写自己的 ClassLoader

Ø  方法:

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

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

 

(三)Class Unloading

Ø  JDK8JDK11中都有-XX:+ClassUnloading (default true)

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

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

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

 

(四)向JDK11迁移

Ø  JDK8JDK11JDK library中的ClassLoader有所不同

1)ExtClassLoader 更名为了PlatformClassLoader

2)PlatformClassLoaderAppClassLoader不再继承自URLClassLoader

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

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

 

(五)AppCDS (APPlication Class Data Sharing)

Ø  AppCDSOpenJDK做的一个特性,它有以下特点:

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

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

3)JDK10后变为AppCDS,可以用于AppClassLoadercustom 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的目录都可以搜到。

相关文章
|
6月前
|
Java
类加载器以及类的加载过程
这篇文章讨论了Java中的类加载器机制以及类的加载过程。
类加载器以及类的加载过程
|
5月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
132 35
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
5月前
|
Arthas 前端开发 Java
类加载器 超详解:什么是类加载器,类加载器作用及应用场景,类加载时机,类加载的完整过程,类加载器分类
类加载器 超详解:什么是类加载器,类加载器作用及应用场景,类加载时机,类加载的完整过程,类加载器分类
类加载器 超详解:什么是类加载器,类加载器作用及应用场景,类加载时机,类加载的完整过程,类加载器分类
|
4月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
166 3
|
9月前
|
监控 安全 Java
JVM工作原理与实战(九):类加载器-启动类加载器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了启动类加载器、通过启动类加载器去加载用户jar包等内容。
101 8
|
9月前
|
存储 缓存 前端开发
类加载与类加载器概述
类加载与类加载器概述
71 6
|
前端开发 Java 数据库连接
【面试题精讲】JVM-类加载器-扩展类加载器
【面试题精讲】JVM-类加载器-扩展类加载器
|
存储 安全 Java
类加载器与类的加载过程
类加载器与类的加载过程
|
前端开发 Java 编译器
类加载器系列(一)——类加载器的作用和分类
类加载器系列(一)——类加载器的作用和分类
328 1
类加载器系列(一)——类加载器的作用和分类
|
存储 安全 前端开发
类加载器及反射简单笔记
类加载器及反射简单笔记
104 0