开发者学堂课程干货总结——Java 虚拟机原理(三)

简介: Java 虚拟机原理课时1.3——类加载器原理。通过本节课的学习,能够掌握Java 虚拟机原理,学习JNI、类加载器原理、safepoint机制等知识。 电子书+视频为同学带来最佳学习效果,文字、课程链接、图谱地址统统为大家放送了哦!

各位同学,开发者学堂Java 图谱中Java 高级工程师篇的课程“Java 虚拟机原理”的课程给开始更新了,第三课时“类加载器原理”的干货总结来啦!一起学习新课程吧!

课程链接以及图谱地址小编已经为大家指路了,搭配学习效果更佳👇

课程名称:加载器原理

课程地址:https://developer.aliyun.com/learning/course/56/detail/1066

图谱名称:Alibaba Java 技术图谱

图谱地址:https://developer.aliyun.com/graph/java


类加载器原理


一、类加载

(一)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 当前类:

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

image.png

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

image.png

image.png

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

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

三、初始化

(一)初始化操作

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

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

2)调用 class 的  方法;

3)实例化。

image.png

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

上图是一个的方法,截取的是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)> Ap

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

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

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

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

相关文章
|
1月前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
271 0
|
2月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
370 1
|
2月前
|
人工智能 Java 开发者
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
JManus是阿里开源的Java版OpenManus,基于Spring AI Alibaba框架,助力Java开发者便捷应用AI技术。支持多Agent框架、网页配置、MCP协议及PLAN-ACT模式,可集成多模型,适配阿里云百炼平台与本地ollama。提供Docker与源码部署方式,具备无限上下文处理能力,适用于复杂AI场景。当前仍在完善模型配置等功能,欢迎参与开源共建。
1434 58
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
|
2月前
|
缓存 Java 开发者
Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛
本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。
192 3
|
3月前
|
Java 测试技术 API
2025 年 Java 开发者必知的最新技术实操指南全览
本指南涵盖Java 21+核心实操,详解虚拟线程、Spring Boot 3.3+GraalVM、Jakarta EE 10+MicroProfile 6微服务开发,并提供现代Java开发最佳实践,助力开发者高效构建高性能应用。
649 4
|
5月前
|
SQL 人工智能 Java
阿里云百炼开源面向 Java 开发者的 NL2SQL 智能体框架
Spring-ai-alibaba-nl2sql 是析言 GBI 产品在数据问答领域的一次重要开源尝试,专注于 NL2SQL 场景下的核心能力开放。
1731 48
|
3月前
|
人工智能 自然语言处理 Java
面向 Java 开发者:2024 最新技术栈下 Java 与 AI/ML 融合的实操详尽指南
Java与AI/ML融合实践指南:2024技术栈实战 本文提供了Java与AI/ML融合的实操指南,基于2024年最新技术栈(Java 21、DJL 0.27.0、Spring Boot 3.2等)。主要内容包括: 环境配置:详细说明Java 21、Maven依赖和核心技术组件的安装步骤 图像分类服务:通过Spring Boot集成ResNet-50模型,实现REST接口图像分类功能 智能问答系统:展示基于RAG架构的文档处理与向量检索实现 性能优化:利用虚拟线程、GraalVM等新技术提升AI服务性能 文
389 0
|
6月前
|
Oracle Java 关系型数据库
JVM深入原理(一+二):JVM概述和JVM功能
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行。
194 0
|
6月前
|
Arthas 存储 Java
JVM深入原理(三+四):JVM组成和JVM字节码文件
目录3. JVM组成3.1. 组成-运行时数据区3.2. 组成-类加载器3.3. 组成-执行引擎3.4. 组成-本地接口4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
126 0