《Java应用提速(速度与激情)》——四、JDK提速

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 《Java应用提速(速度与激情)》——四、JDK提速

1. AppCDS

 

1) 现状

 

CDSClass Data Sharing在Oracle JDK1.5被首次引入,在Oracle JDK8u40中引入了AppCDS,支持JDK以外的类但是作为商业特性提供。随后Oracle将AppCDS贡献给了社区,在JDK10中CDS逐渐完善,也支持了用户自定义类加载器又称AppCDS v2

 

目前CDS在阿里的落地情况:

 

热点应用A使用CDS减少了10秒启动时间

云产品SAE和FC在使用Dragonwell11时开启CDS、AOT等特性加速启动

 

经过十年的发展,CDS已经发展为一项成熟的技术。但是很容易令人不解的是CDS不管在阿里的业务还是业界即便是AWS Lambda都没能被大规模使用。关键原因有两个

 

a) AppCDS在实践中效果不明显

 

jsa中存储的InstanceKlass是对class文件解析的产物。对于boot classloader加载jre/lib/rt.jar下面的类的类加载器和systemapp类加载器加载-classpath下面的类的类加载器,CDS有内部机制可以跳过对class文件的读取,仅仅通过类名在jsa文件中匹配对应的数据结构。

 

Java语言还提供用户自定义类加载器custom class loader的机制,用户通过Override自己的Classloader.loadClass()查找类,AppCDS在为customer class loade时加载类是需要经过如下步骤

 

调用用户定义的Classloader.loadClass(),拿到class byte stream

计算class byte stream的checksum,与jsa中的同类名结构的checksum比较

如果匹配成功则返回jsa中的InstanceKlass,否则继续使用slow path解析class文件

 

b) 工程实践不友好

 

使用AppCDS需要如下步骤

 

针对当前版本在生产环境启动应用,收集profiling信息

基于profiling信息生成jsajava sahred archivedump

将jsa文件和应用本身打包在一起,发布到生产环境

 

由于这种trace-replay模式的复杂性,在SAE和FC云产品的落地都是通过发布流程的定制以及开发复杂的命令行工具来解决的。

 

2) 解决方案

 

针对上述的问题1,在热点应用A上CDS配合JarIndex或者使用编译器团队开发的EagerAppCDS特性原理见5.1.3.1都能让CDS发挥最佳效果。

 

经验证,在热点应用A已经使用JarIndex做优化的前提下进一步使用EagerAppCDS依然可以获得15秒左右的启动加速效果。

 

3) 原理

 

面向对象语言将对象数据和方法对象上的操作绑定到了一起,来提供更强的封装性和多态。这些特性都依赖对象头中的类型信息来实现,Java、Python语言都是如此。Java对象在内存中的layout如下:

+-------------+
|  mark       |
+-------------+
|  Klass*     |
+-------------+
|  fields     |
|             |
+-------------+

 

mark表示了对象的状态,包括是否被加锁、GC年龄等等。而Klass*指向了描述对象类型的数据结构InstanceKlass

/  InstanceKlass layout:
//    [C++ vtbl pointer           ] Klass
//    [java mirror                ] Klass
//    [super                      ] Klass
//    [access_flags               ] Klass
//    [name                       ] Klass
//    [methods                    ]
//    [fields                     ]
...

 

基于这个结构,诸如o instanceof String这样的表达式就可以有足够的信息判断了。要注意的是InstanceKlass结构比较复杂,包含了类的所有方法、field等等,方法又包含了字节码等信息。这个数据结构是通过运行时解析class文件获得的,为了保证安全性,解析class时还需要校验字节码的合法性非通过javac产生的方法字节码很容易引起jvm crash

 

CDS可以将这个解析、校验产生的数据结构存储dump到文件,在下一次运行时重复使用。这个dump产物叫做Shared Archive,以jsa后缀java shared archive

 

为了减少CDS读取jsa dump的开销,避免将数据反序列化到InstanceKlass的开销,jsa文件中的存储layout和InstanceKlass对象完全一样,这样在使用jsa数据时,只需要将jsa文件映射到内存,并且让对象头中的类型指针指向这块内存地址即可,十分高效。

Object:
+-------------+
|  mark       |         +-------------------------+
+-------------+         |classes.jsa file         |
|  Klass*     +--------->java_mirror|super|methods|
+-------------+         |java_mirror|super|methods|
|  fields     |         |java_mirror|super|methods|
|             |         +-------------------------+
+-------------+

 

a) Alibaba Dragonwell对AppCDS的优化

 

上述AppCDS for custom classloader的加载流程更加复杂的原因是JVM通过classloaderclassName二元组来唯一确定一个类。

 

对于BootClassloader、AppClassloader在每次运行都是唯一的,因此可以在多次运行之间确定唯一的身份

对于customClassloader除了类型,并没有明显的唯一标识。AppCDS因此无法在加载类阶段通过classloader对象和类型去shared archive定位到需要的InstanceKlass条目。

 

Dragonwell提供的解决方法是让用户为customClassloader标识唯一的identifier加载相同类的classloader在多次运行间保持唯一的identifier。并且扩展了shared archive,记录用户定义的classloader identifier字段,这样AppCDS便可以在运行时通过identifierclassName二元组来迅速定位到shared archive中的类条目。从而让custom classloader下的类加载能和buildin class一样快。

 

在常见的微服务workload下,我们可以看到Dragonwell优化后的AppCDS将基础的AppCDS的加速效果从10%提升到了40%。

 

2. 启动profiling工具

 

1) 现状

 

目前有很多Java性能剖析工具,但专门用于Java启动过程分析的还没有。不过有些现有的工具,可以间接用于启动过程分析,由于不是专门的工具,每个都存在这样那样的不足。

 

比如async-profiler,其强项是适合诊断CPU热点、墙钟热点、内存分配热点、JVM内锁争抢等场景,展现形式是火焰图。可以在应用刚刚启动后,马上开启aync-profiler,持续剖析直到应用启动完成。async-profiler的CPU热点和墙钟热点能力对于分析启动过程有很大帮助,可以找到占用CPU较多的方法,进而指导启动加速的优化。

 

async-profiler有2个主要缺点

 

第1个是展现形式较单一,关联分析能力较弱,比如无法选择特定时间区间,也无法支持选中多线程场景下的火焰图聚合等。

第2个是采集的数据种类较少,看不到类加载、GC、文件IO、SocketIO、编译、VM Operation等方面的数据,没法做精细的分析。

 

再比如arthas,arthas的火焰图底层也是利用async-profiler,所以async-profiler存在的问题也无法回避。

 

最后我们自然会想到OpenJDK的JDK Flight Recorder,简称JFR。AJDK8.5.10+和AJDK11支持JFR。JFR是JVM内置的诊断工具,类似飞机上的黑匣子,可以低开销的记录很多关键数据,存储到特定格式的JFR文件中,用这些数据可以很方便的还原应用启动过程,从而指导启动优化。JFR的缺点是有一定的使用门槛,需要对虚拟机有一定的理解,高级配置也较复杂,同时还需要搭配桌面软件Java Mission Control才能解析和阅读JFR文件。

 

面对上述问题,JVM工具团队进行了深入的思考,并逐步迭代开发出了针对启动过程分析的技术产品。

 

2) 解决方案

 

我们选择JFR作为应用启动性能剖析的基础工具。JFR开销低,内建在JDK中无第三方依赖,且数据丰富。JFR会周期性记录Running状态的线程的栈,可以构建CPU热点火焰图。JFR也记录了类加载、GC、文件IO、SocketIO、编译、VM Operation、Lock等事件,可以回溯线程的关键活动。对于早期版本JFR可能存在性能问题的特性,我们也支持自动切换到aync-profiler以更低开销实现相同功能。

 

为了降低JFR的使用门槛,我们封装了一个javaagent,通过在启动命令中增加javaagent参数,即可快速使用JFR。我们在javaagent中内置了文件收集和上传功能,打通数据收集、上传、分析和交互等关键环节,实现开箱即用。

 

我们开发了一个Web版本的分析器(或者平台),它接收到javaagent收集上传的数据后,便可以直接查看和分析。我们开发了功能更丰富和易用的火焰图和线程活动图。在类加载和资源文件加载方面我们也做了专门的分析,类似URLClassLoader在大量Jar包场景下的Class Loading开销大、Tomcat的WebAppClassLoader在大量jar包场景下getResource开销大、并发控制不合理导致锁争抢线程等待等问题都变得显而易见,未来还将提供评估开启CDS(Class Data Sharing)以及JarIndex后可以节省时间的预估能力。

 

3) 原理

 

当Oracle在OpenJDK11上开源了JDK Flight Recorder之后,阿里巴巴也是作为主要的贡献者,与社区包括RedHat等,一起将JFR移植到了OpenJDK 8。

 

JFR是OpenJDK内置的低开销的监控和性能剖析工具,它深度集成在了虚拟机各个角落。

 

JFR由两个部分组成:

 

第1个部分分布在虚拟机的各个关键路径上,负责捕获信息

第2个部分是虚拟机内的单独模块,负责接收和存储第1个部分产生的数据。

 

这些数据通常也叫做事件。JFR包含160种以上的事件。JFR的事件包含了很多有用的上下文信息以及时间戳。比如文件访问,特定GC阶段的发生,或者特定GC阶段的耗时,相关的关键信息都被记录到事件中。

 

尽管JFR事件在他们发生时被创建,但JFR并不会实时的把事件数据存到硬盘上,JFR会将事件数据保存在线程变量缓存中,这些缓存中的数据随后会被转移到一个global ring buffer。当global ring buffer写满时,才会被一个周期性的线程持久化到磁盘。

 

虽然JFR本身比较复杂,但它被设计为低CPU和内存占用,总体开销非常低,大约1%甚至更低。所以JFR适合用于生产环境,这一点和很多其它工具不同,他们的开销一般都比JFR大。

 

JFR不仅仅用于监控虚拟机自身,它也允许在应用层自定义事件,让应用程序开发者可以方便的使用JFR的基础能力。

 

有些类库没有预埋JFR事件,也不方便直接修改源代码,我们则用javaagent机制,在类加载过程中,直接用ASM修改字节码插入JFR事件记录的能力。比如Tomcat的WebAppClassLoader,为了记录getResource事件,我们就采用了这个方法。

 

整个系统的结构如下:

 

image.png 

相关文章
|
2月前
|
人工智能 安全 Java
Java和Python在企业中的应用情况
Java和Python在企业中的应用情况
75 7
|
21天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
56 2
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
202 6
|
1月前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
43 2
|
2月前
|
Oracle 安全 Java
深入理解Java生态:JDK与JVM的区分与协作
Java作为一种广泛使用的编程语言,其生态中有两个核心组件:JDK(Java Development Kit)和JVM(Java Virtual Machine)。本文将深入探讨这两个组件的区别、联系以及它们在Java开发和运行中的作用。
123 1
|
2月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
77 6
|
2月前
|
关系型数据库 MySQL Java
MySQL索引优化与Java应用实践
【11月更文挑战第25天】在大数据量和高并发的业务场景下,MySQL数据库的索引优化是提升查询性能的关键。本文将深入探讨MySQL索引的多种类型、优化策略及其在Java应用中的实践,通过历史背景、业务场景、底层原理的介绍,并结合Java示例代码,帮助Java架构师更好地理解并应用这些技术。
65 2
|
2月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
Java 测试技术 API
Java 反射机制:深入解析与应用实践
《Java反射机制:深入解析与应用实践》全面解析Java反射API,探讨其内部运作原理、应用场景及最佳实践,帮助开发者掌握利用反射增强程序灵活性与可扩展性的技巧。
142 4
|
2月前
|
Java BI API
Java Excel报表生成:JXLS库的高效应用
在Java应用开发中,经常需要将数据导出到Excel文件中,以便于数据的分析和共享。JXLS库是一个强大的工具,它基于Apache POI,提供了一种简单而高效的方式来生成Excel报表。本文将详细介绍JXLS库的使用方法和技巧,帮助你快速掌握Java中的Excel导出功能。
81 6