[转帖]【技术剖析】12. 毕昇 JDK 8 中 AppCDS 实现介绍

技术,剖析,毕昇,jdk,appcds,实现,介绍 · 浏览次数 : 0

小编点评

**AppCDS 使用说明** 1. 在 java 程序被 JVM 执行的过程中,会不断地从 classpath 中加载 class,并在 JVM 内部创建对应的 klass 对象。 2. Class 对象创建成功之后 JVM 会把 class 在 JVM 内部的 qualified name 写入到 class.lst 文件中,比如 java.lang.Objectd 对应的是 java/lang/Object. 3. 使用 AppCDS 或者 CDS 的时候不能 debug java 程序。 4. AppCDS 效果TPC-DS 测试套的 10 个 sql 在 5T 数据的场景下测试了一下 AppCDS 的效果,结果收益好,最低是 8.42% 的性能提升,最高是 26.9% 的性能提升。 5. 注意事项使用 AppCDS 三部曲过程中要保持 JVM 版本、JAVA_HOME 路径一致,否则会校验失败,无法启动 JVM。 **使用 AppCDS 的步骤** 1. 在 java 程序被 JVM 执行的过程中,加载 jsa 归档文件。 2. 创建 class 文件。 3. 使用 AppCDS 或 CDS 的方法加载 class 文件。 4. 创建 jsa 归档文件。 5. 使用 jsa 中的 class 信息加载对应的 class 对象。

正文

https://bbs.huaweicloud.com/forum/thread-169622-1-1.html

 

作者:伍家华

> 编者按:笔者通过在 Hive 的场景发现 AppCDS 技术存在的价值,然后分析了 AppCDS 的工作原理,并将 JDK 11 中的特性移植到毕昇 JDK 8,在移植过程中由于 JDK 8 和 JDK 11 在类加载实现有所不同,JDK 11 在加载过程中增强了安全性检查,为了达到相同的效果没有对 JDK 8 中的类加载进行修改,而是通过额外的安全检查保证共享类的安全性。最后笔者介绍了如何使用 AppCDS 以及使用的注意事项,希望对读者有所帮助。

基于某产品集群业务中有使用 Hive 场景,我们发现该集群在执行任务时会启动大量的 Java 进程,且进程很快就执行结束。对于这种情况一个有效的解决方法是让 Java 进程之间重用部分公共数据,从而减少 Java 进程产生公共数据的耗时。在 JDK 11 中支持 CDS 和 AppCDS,CDS 的全称是 Class-Data Sharing,CDS 的作用是可以让类被预处理放到一个归档文件中,后续 Java 程序启动的时候可以直接带上这个归档文件,这样 JVM 可以直接将这个归档文件映射到内存中,以节约应用启动的时间。AppCDS 是 CDS 的扩展,不止能够作用于 Boot Class Loader,App Class Loader 和自定义的 Class Loader 也都能够起作用,加大了 CDS 的适用范围 [1]。技术原理是让多个 Java 进程共享类的元数据,其基本原理如下所示:

image.png

左右两边分别都是一个 Java 进程,双方通过共享内存达到节约内存、减少类解析的目的。下面通过案例介绍一下我们发现的现象,然后再介绍类共享机制的内部原理。

1. 技术背景

1.1 环境介绍

Hive on tez 集群,4 个节点,一个管理节点,其他三个节点作为工作节点。软件版本:Hadoop 3.1.0,hive 3.1.2,tez-0.9.1,ambari2.6,TPC-DS 标准测试套。硬件环境:Kunpeng 920,128 核,512G 内存。

1.2 系统负载情况

运行 TPC-DS 自带的 sql8,使用 top 观察其中的一个节点的工作负载,发现启动了大量的 JVM 进程。

image.png

1.3 热点分布

同时使用 perf 采集整机的热点数据,发现 JVM 的 C1, C2 编译线程占比很高,如下图所示。因此判断 hive sql 的 startup 和 warmup 阶段占比比较高,可以考虑从这两个方向优化。

image.png

考虑到目前 JVM 的 C2 代码比较难维护,也很难引进新的 feature。因此考虑看看在启动阶段有没有优化的可能。jdk1.8 带有 cds 功能,能够 share jdk 自带的 java class,能够减少 JVM 的类加载时间,因此首先尝试使用默认的 cds 功能,测试发现效果不明显。原因是 hive sql 执行过程中加载的类大部分是应用程序 classpath 下的类,jdk 自身的类占比很小,因此收益不明显。考虑到 jdk10 以后有了 AppCDS 功能,能够 share classpath 下的 class,因此考虑在 jdk1.8 上面实现 AppCDS 功能。

2. AppCDS 实现

毕昇 JDK 支持 AppCDS,即在原生 CDS 的基础上也支持用户自己程序 classpath 中的类共享。原生 CDS 只支持由 Bootstrap classloader 和 Ext Classloader 加载的 class,即 jdk 自带的 class 在多进程之间共享。但是在实际场景中,尤其是在使用了大量开源软件的情况下,用户指定 classpath 下的 jar 远远大于 jdk 自带的 class,这样 CDS 的实际效果比较有限。

AppCDS 能够扩大 class 共享的范围,从而在节省内存使用以及 JVM 的启动时间两个方面进一步提升效果。

2.1 create class list 文件

java class 文件被 JVM 执行之前首先要被加载进 JVM 内存里面,作为 meta data 存储在 JVM 的 metaspace 内存区域。其中一个关键步骤就是解析 class 文件。JVM 内部有个 ClassFileParser 类,提供了 parseClassFile 方法,解析成功之后会生成一个对应 Klass。AppCDS 的第一步逻辑就是在类被解析完成之后,如果 class 不是匿名类,且 class 文件的 major version 大于 JAVA_1_5 Version,则会把 class 对应的 qualified name 写去到 list 文件中去。

image.png

2.2 create jsa 归档文件

image.png

在这一步 JVM 会按行读取 appcds.lst 文件中内容,然后使用 App ClassLoader 去加载对应的 class。由于 Java classloader 自身的 delegate 机制,能够确保 jdk 自身的 class 也能够得到加载。Class 被加载之后存在 JVM 的 metaspace 区域,这个 metaspace 是 JVM Native Memory 的一部分,里面包含 klass, ConstantPool, Annotation, ConstMethod, Method, MethodData 等 meta 信息。

当 lst 文件中对应的 class 都被 JVM 加载完成之后,JVM 会把其对应的 metadata 写入到从虚拟地址 0x800000000 开始的内存区域,其格式是 JVM 内部私有的一种表示,然后再把这部分数据 dump 到归档文件中。

2.3 与 JDK 11 实现的差别

与 JDK 11 中的 AppCDS 实现差别主要体现在加载用户指定的 class 逻辑上。当加载用户 class 时,是需要计算获取一个 ProtectionDomain 来做安全验证的。JDK 11 是直接在 hotspot 中使用 JavaCalls 模块构造这个 ProtectionDomain,而毕昇 JDK 8 是在 Java 的 ClassLoader 中提供了一个 getProtectionDomainInternal 函数来获取,并在加载 class 的时候使用 JavaCalls 调用这个 java 方法。

3. AppCDS 使用说明

3.1 创建 lst 文件

启动 JVM 的时候添加 - Xshare:off 和 - XX:+DumpLoadedClassList=/path/to/class.lst 这两个选项。这里比较重要的是第二个选项,这个选项同时支持 %P,可以按进程生成 class.lst 文件,生成的文件名会带上 JVM 进程的 pid。class.lst 本质上是个纯文本文件,里面记录 JVM 运行过程中加载的 class 列表。在 java 程序被 JVM 执行的过程中,会不断地从 classpath 中加载 class,并在 JVM 内部创建对应的 klass 对象。Klass 对象创建成功之后 JVM 会把 class 在 JVM 内部的 qualified name 写入到 class.lst 文件中,比如 java.lang.Objectd 对应的是 java/lang/Object。

使用例子:

java -Xms16M -Xmx16M -XX:+UseG1GC -cp test.jar -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=appcds.lst com.example.App

3.2 创建 archive 文件

第二步:创建 cds 归档文件。使用方式:

java -Xms16M -Xmx16M -XX:+UseG1GC -cp test.jar -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=appcds.lst -XX:SharedArchiveFile=appcds.jsa com.example.App

3.3 使用 archive 文件

使用示例:

java -Xms16M -Xmx16M -XX:+UseG1GC -cp test.jar -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=appcds.jsa com.example.App

第三步:JVM 在启动的过程中会把 jsa mmap 到内存中,然后在触发类加载时候 JVM 会首先尝试从这块 mmap 进来的内存中去获取 klass 对象,如果获取到则会更新 JVM 内部的 SystemDictionary,以后再来获取对应的 klass 对象则不需要从 jsa 中获取。如果获取失败,JVM 则会走正常的类加载流程,尝试从 classpath 中加载对应的 class 文件,解析,链接,初始化,然后再放入 SystemDictionary 中。

4. AppCDS 效果

TPC-DS 测试套的 10 个 sql 在 5T 数据的场景下测试了一下 AppCDS 的效果,结果收益好,最低是 8.42% 的性能提升,最高是 26.9% 的性能提升。

image.png

5. 注意事项

使用 AppCDS 三部曲过程中要保持 JVM 版本、JAVA_HOME 路径一致,不然会校验失败,无法启动 JVM。 在第二步加载 class 并创建 jsa 归档文件的时候,JVM 会记录 class 的 fingerprint 信息 (相当于 hash 值),在第三步使用 jsa 中的 class 信息时会校验这个 fingerprint 信息,因此如果业务侧代码发生变更,要重新部署的话,jsa 归档文件要重新制作。 使用 AppCDS 或者 CDS 的时候不能 debug java 程序。因为 debug 的时候 jvmti 会修改 class 对应的内容,而根据第二点,必然会导致 fingerprint 不一致。 目前 AppCDS 并不支持 Custom ClassLoader,因此在 tomcat、jetty、SpringBoot 这些使用较多 Custom ClassLoader 场景的,整体收益跟原生 CDS 应该差不多,class 可以 share 的范围仅仅多了一些 Application 自身的 boot class。

后记

如果遇到相关技术问题(包括不限于毕昇 JDK),可以进入毕昇 JDK 社区查找相关资源(点击阅读原文进入官网),包括二进制下载、代码仓库、使用教学、安装、学习资料等。毕昇 JDK 社区每双周周二举行技术例会,同时有一个技术交流群讨论 GCC、LLVM、JDK 和 V8 等相关编译技术,感兴趣的同学可以添加如下微信小助手,回复 Compiler 入群。

与[转帖]【技术剖析】12. 毕昇 JDK 8 中 AppCDS 实现介绍相似的内容:

[转帖]【技术剖析】12. 毕昇 JDK 8 中 AppCDS 实现介绍

https://bbs.huaweicloud.com/forum/thread-169622-1-1.html 作者:伍家华 > 编者按:笔者通过在 Hive 的场景发现 AppCDS 技术存在的价值,然后分析了 AppCDS 的工作原理,并将 JDK 11 中的特性移植到毕昇 JDK 8,在移植

[转帖]【技术剖析】15. Native Memory Tracking 详解(1):基础介绍

https://bbs.huaweicloud.com/forum/thread-0246998875346680043-1-1.html 0.引言 我们经常会好奇,我启动了一个 JVM,他到底会占据多大的内存?他的内存都消耗在哪里?为什么 JVM 使用的内存比我设置的 -Xmx 大这么多?我的内存

[转帖]【技术剖析】16. Native Memory Tracking 详解(2):追踪区域分析(一)

https://bbs.huaweicloud.com/forum/thread-0295101552606827089-1-1.html 上篇文章 Native Memory Tracking 详解(1):基础介绍 中,分享了如何使用NMT,以及NMT内存 & OS内存概念的差异性,本篇将介绍NM

[转帖]【技术剖析】17. Native Memory Tracking 详解(3):追踪区域分析(二)

https://bbs.huaweicloud.com/forum/thread-0227103792775240073-1-1.html 应用性能调优 发表于 2022-11-14 15:19:36143查看 上篇文章 Native Memory Tracking 详解(2):追踪区域分析(一) 

[转帖]【技术剖析】18. Native Memory Tracking 详解(4):使用 NMT 协助排查内存问题案例

https://bbs.huaweicloud.com/forum/thread-0211103793043202049-1-1.html 其他 发表于 2022-11-14 15:38:571174查看 从前面几篇文章,我们了解了 NMT 的基础知识以及 NMT 追踪区域分析的相关内容,本篇文章将

[转帖]【技术剖析】8. 相同版本 JVM 和 Java 应用,在 x86 和AArch64 平台性能相差30%,何故?

https://bbs.huaweicloud.com/forum/thread-168532-1-1.html 作者: 吴言 > 编者按:目前许多公司同时使用 x86 和 AArch64 2 种主流的服务器。这两种环境的算力相当,内存相同的情况下:相同版本的 JVM 和 Java 应用,相同的 J

[转帖]【技术剖析】10. JVM 中不正确的类加载顺序导致应用运行异常问题分析

https://bbs.huaweicloud.com/forum/thread-169439-1-1.html 神Bug... 发表于 2021-11-15 10:36:113973查看 作者:程经纬、谢照昆 > 编者按:两位笔者分享了不同的案例,一个是因为 JDK 小版本升级后导致运行出错,最终

[转帖]【技术剖析】11. 使用jemalloc解决JVM内存泄露问题

https://bbs.huaweicloud.com/forum/thread-169523-1-1.html 作者:王坤 > 编者按:JVM 发生内存泄漏,如何能快速定位到内存泄漏点并不容易。笔者通过使用 jemalloc(可以替换默认的 glibc 库)中的 profiling 机制(通过对程

[转帖]【技术剖析】9. 使用 NMT 和 pmap 解决 JVM 资源泄漏问题

https://bbs.huaweicloud.com/forum/thread-168749-1-1.html 作者:宋尧飞 > 编者按:笔者使用 JDK 自带的内存跟踪工具 NMT 和 Linux 自带的 pmap 解决了一个非常典型的资源泄漏问题。这个资源泄漏是由于 Java 程序员不正确地使

[转帖]【技术剖析】5. JDK 从8升级到11,使用 G1 GC,HBase 性能下降近20%。JDK 到底干了什么?

https://bbs.huaweicloud.com/forum/thread-145649-1-1.html 发表于 2021-08-04 10:22:135894查看 作者:林军军、彭成寒 编者按:笔者在 HBase 业务场景中尝试将 JDK 从 8 升级到 11,使用 G1 GC 作为垃圾回