走进jvm之垃圾回收器篇

        这里我想首先说明一下,虽然我们经常会拿垃圾回收器来做比较,虽然想挑选一个最好的收集器出来,但是目前也没有说哪一款收集器是完美的,更不存在万能的收集器,我们也只是对收集器选择最适合场景的一个收集器。

        那么作者将在接下来和大家探讨一些垃圾回收器的发展历程和优缺点,相信读完这篇的你也绝对不是白花时间,不过理解的过程有点枯燥,也希望大家有耐心看完,或者进行完善。

Serial收集器

        这款收集器可谓是最原始的垃圾收集器了,这个收集器在回收的时候只会使用一个处理器或者一条收集线程,直到收集结束。也就是说用户的线程是可以并行的,但是在新生代和老年代的收集都是单线程GC,新生代采用复制,老年代采用标记整理,也就是说只要收集垃圾就会stop the world并且不可控还不可知,这对于用户来说简直是灾难。

        那大师们首先想到的思路就是,既然用户线程都可以进行多线程了,那么垃圾回收的时候如果也进行多线程,会不会更快呢?因此ParNew收集器就来了。

ParNew收集器

        这里笔者也就不画图了,它和serial的区别就是在gc线程的时候,在新生代采用复制算法,并且用多线程并行收集。但是在老年代依然是采用标记整理算法的单线程。但是不得不说,在jdk7之前,除了serial收集器只有它能和cms收集器配合工作。

        在jdk5发布的时候,hotsopt也推出了一个cms收集器,这个可以说是具有划时代的意义了。但遗憾的是,cms作为老年代收集器,却没有办法与jdk4中存在的新生代Parallel Scavenge配合工作。所以在jdk5中使用cms来收集老年代,新生代只能选择ParNew或者Serial收集器中的一个。parNew也算是抱上了cms的大腿。以后的出场它俩也是搭配出场。但遗憾的是什么,就是在g1回收器出来之后,就不需要其他新生代收集器配合了,因为g1是面向全堆的。

        通过堆parnew的了解,也知道,parnew在单核情况下其实是和serial差不多的。

Parallel Scavenge收集器

        Parallel Scavenge是一款新生代的收集器,是采用标记-复制算法实现的,同样从名字看出来它也是可以实现并行收集的回收器。但是我们要探究一下他和parnew的区别在哪里。

        Parallel Scavenge的特点就是关注点和其他的不同,cms关注的是延迟也就是说尽可能缩短垃圾回收时候用户所暂停的时间,而Parallel Scavenge面向的是一个希望达到一个可控的吞吐量。吞吐量也就是处理器用于运行用户代码的时间和处理器总耗时的比值。当然是希望用户运行代码的时间越长越好。因此这款收集器也常常被称为吞吐量优先的垃圾处理器。

        这里呢也可以提出一个参数,如果我们自己把我不好新生代伊甸园和幸存区的比例,新生代大小或者晋升老年大对象的大小,就激活参数-XX:+UseAdaptiveSizePolicy就可以了这个开关打开之后虚拟机会根据当前系统的运行情况去动态设置的。

Serial Old收集器

        serial old收集器是serial收集器的老年代版本,同样的他也是一个单线程收集器,使用标记整理算法。这个垃圾收集器的意义就是供客户端模式下的虚拟机用。在服务端的话,jdk之前可以配合 Parallel Scavenge,另外一种就是作为cms收集器失败之后发生fullGc等等的后备方案

Parallel Old收集器

        这个收集器看名字就直到是Parallel Scavenge的老年代版本,采用标记整理算法,这个是支持多线程并行收集的,直到jdk6才有。不然的话往前看Parallel Scavenge其实很尴尬啊,如果新生代选择了这个那么老年代除了serial old(PS MarkSweep)意外就没得选啦,cms就不好使啊。如果但是用Parallel Scavenge作为新生代的话,由于serial old太垃圾的话也没有办法充分利用服务端的多处理器并行能力,直到这个Parallel Old出来了,吞吐量优先才算是脑子和脖子接上了,表示我自己还没有适应这具躯体哈哈哈。

注意第一个是新生代Parallel,是采用的复制算法。后面的才是老年代gc的标记整理。

CMS收集器

        接下来我们终于介绍到了小时代1.0的cms收集器了。在之前提到的垃圾回收器的时候,优化点在于把单线程的垃圾回收工作转为多线程?那么有没有更好的优化方式呢?随着科技的进步答案显而易见,就是我们可不可以让垃圾回收的过程和用户线程一起并发呢?这就是需要很大的挑战了,下面我们来看看cms是怎么做的。

        concurrent mark sweep可以看出,这个是基于标记清除的垃圾处理器,因此下意思的反应就是它是否会产生内存碎片呢?答案是肯定的。

        cms的回收工作可以拆分成四个过程,初始标记,并发标记,重新标记,并发清除。除了带并发两个字的,其他的都是需要stop world的。

        但是初始标记过程只是标记一下GC Roots能关联到的对象,这个速度是很快的,而并发标记就是从GcRoot直接关联的对象开始遍历整个对象图的过程。这个过程耗时较长但是不需要停顿用户线程。而重新标记阶段则是要修正并发标记期间,因为用户线程可以与垃圾回收器一起工作和产生变动的那一部分对象,这个是需要重新标记的,这个阶段的停顿时间比初始标记稍长但也远比并发标记的时间要短。最后就是并发清除阶段,用于清除已经标记为死亡的对象,这个阶段也是可以与用户线程进行并发的。

        这里面耗时最长的就是并发标记和并发回收了。总体上来说,cms内存回收阶段是和用户线程一起进行的。

缺点

但是我们也不得不提到几个问题

1.cms处理器既然不回导致用户停顿,肯定是因为占用一部分线程或者说是处理器,那么应用程序就会变慢,因为资源是有限的不可避免吞吐量会降低。也就是说处理器的核心数非常重要,太少了就别来擦边了。那么为了缓解这种情况,虚拟机也提供了一种增量式并发收集器,主打在并发标记和清理的时候让收集器线程和用户线程交替执行,就是要干活就只有一个干活,别一起上。这样只管速度感受就好一些,但这个已经不建议使用了。

2.由于用户线程和并发标记和并发清理阶段线程是并行的,所以不可避免地就会造成有一部分对象是无法进行回收,只能留着下一次回收,这种垃圾就是浮动垃圾。因此cms收集器不能像其他处理器一样等老年代几乎填满了再进行收集,而是必须预留一部分空间供并发收集时地程序使用。如果cms运行期间预留地空间无法分配对象了,那么不好意思只能并发失败冻结用户线程,临时用serial old去重新收集老年代,这样用户停顿时间就会很长,几乎game over了。

3.在开头我们就已经提到了标记删除不可避免地会带来空间碎片,空间不连续的问题,这个也会造成我们之前提到地full gc的问题。

Garbage First收集器

G1收集器又是一个划时代的垃圾回收器。简称小时代2.0版本吧。他有一个很大的思想转变就是把内存的区域化整为零,采用面向局部收集,基于region的内存布局形式。g1是主要面向服务端的垃圾回收器。

它的阶段可以分为,初始标记,并发标记,最终标记,筛选回收。采用复制整理算法。这里可以知道这个是没有内存碎片的。

jdk9发布时直接取代了PS+PS Old了。成为默认垃圾回收器。这里主要讲解它的闪光点和它的缺点。

        一款可预测暂停时间的停顿预测模型。这里只指定一个长度为m毫秒的时间片段内,消耗在垃圾收集上的时间打开率不超过n毫秒这样的目标。

        g1跳出了回收面向的对象要么是老年代要么是新生代的局限,它可以面向堆内存的任何一部分来组成回收集,衡量标准不再是哪个代而是那块内存中垃圾数量最多,回收效益最大就回收哪部分。

        region的每一块是想等大小的,每一个region都可以根据需要来扮演新生代的eden空间,survior空间或者老年代空间。收集器能够根据某一块的角色不同区采用不同的策略。

        region还有一类特殊的就是humongous区域,专门用来存储大对象的。当对象空间大于一region容量一般的时候就可以判定为大对象。而g1对humongous当作老年代来看,如果大小超过了一个的话,他也会用连续的内存空间进行存储。

        G1之所以能建立可预测的停顿模型,是因为它将region作为单词回收的最小单元,即每次收集到的内存空间都是region大小的整数倍,这样可以有计划地避免在整个java堆中进行全区域地垃圾收集。更具体地处理就是让G1跟踪各个region地垃圾堆积地价值大小,然后在后台维护一个优先级表,每次再结合用户允许地收集停顿时间去回收价值收益最大的那些region。

        但是往往将一个整体细化成各个细碎的东西,虽然更灵活,反之带来的代价就是它的维护更麻烦。这个概念就跟单体项目和微服务项目一样。

  问题

1.难维护,耗内存

        将堆分为region中,跨代引用如何解决?我们可以使用记忆集来避免全堆作为GCRoot扫描,但g1收集器毫无疑问会更加的复杂。g1的记忆集本质上是一个哈希表,key是region的起始位置,value是一个集合,存储元素是卡表的索引号。这种双向卡既知道我指向谁,又知道谁指向我。并且由于region的庞大数量,索引g1至少要花费相当于java堆内容10%-20%的内存来维持收集器工作。

2.  如何在并发标记阶段保收集线程与用户线程互不干扰的运行?这里首先要解决的就是用户线程改变对象引用关系的问题,需要保证不能打乱原来的图结构导致结果错误。cms通过增量更新解决,而g1则是通过satb快照进行解决,有点类似与mvcc里的readview参数的快照,这个应该是类似于可重复读级别的快照。而用户线程在运行的时候肯定又会产生新对象,这部分对象region每一个region设计了两个名为tams的指针,把region的一部分空间分出来用来给这部分新对象进行分配。但是如回收速度赶不上对象创建的速度,就又会导致fullGC,与cms类似,冻结用户线程,产生长时间的stop the world。

3.如何建立起可靠的停顿预测模型呢?

        这里最好是更新region的统计状态,这个状态越新肯定效果最好。当然停顿时间不是越短越好,如果垃圾太多却来不及收集,空间没了依然会发生fullGc的。

4.执行负载高

为了实现原始快照satb,还需要使用写前屏障来跟踪并发时的指针变化,相比增量更新,虽然能减少并发标记和重新标记的损耗,但在用户程序运行过程中会产生由跟踪引用变化带来的额外负担。

由于g1的写屏障比cms的复杂很多,所以g1不像cms是同步的,而是采取类似消息队列的结构去异步处理。

最后来看,在小内存的应用上,还是cms比较好,但随着g1的不断优化,这是谁也都说不好的事情。

低延迟垃圾收集器

        这里不得不说一下前面垃圾回收器的思想的转变,我的理解是,由单线程到多线程。由只能暂停用户线程到可以部分和用户线程一起进行,再到对管理的对象化整为零,从整个堆内存变成分region的堆内存。

Shenandoah

        G1已经那么牛逼了。但是我们是不是还有优化空间呢?随着计算机的发展,硬件提升,这个内存量是越来越能容忍大内存了。但是作文衡量垃圾回收器的指标,内存占用,延迟和吞吐量来说,大内存带来的反而是低延迟,因为回收小区和楼道的垃圾的时间是不一样的。那么在和用户并行的阶段,不可避免地问题就是新对象的不断创建和旧对象的地址引用修改。

        Shen的优化思路,能否能让 回收的整个过程都是并发的呢?

因此shen只有在初始标记和最终标记有短暂的停顿,而这个停顿时间基本都是固定的,与GC ROOt对象有关,和堆大小,对象数量没有正比例关系。

        shen和G1有着相似的堆内存布局,思想上几乎是一致,那么我们来说说他们的不同了吧。

1.G1的回收阶段不可以与用户并发,但是shen可以。

2.shen没有实现分代,但并不代表着分代不重要。这个只不过权衡利弊后的结果,不用花费大内存去维护记忆集,而是利用连接矩阵来记录region的跨region引用问题,这也降低了伪共享的概率。

它的细碎阶段分为:初始标记,并发标记,最终标记,并发清理,并发回收,初始引用更新,并发引用更新和并发清理阶段。

其实最重要的阶段可以分为三个:并发标记,并发回收,并发引用更新。

说精华:并发标记是遍历处GcRoot全部可达的对象。时间长短取决于堆中对象存活对象的数量以及对象图结构的复杂度。

并发回收:把存活的对象复制到其他未被使用的region里,这点有点类似与copy on write了。但是旧的引用时很难改变到新的引用的,这里用到了读屏障和转发指针来解决。时间长短取决于会收集的大小。

并发引用更新:与用户线程并发,时间长短取决于内存中涉及的引用数量的多少。并发引用更新和并发标记不同。它不再沿着对象图搜索吗,而是按照内存物理地址顺序,线性搜索索引引用类型,把旧值更改为新的值旧可以了。最后一次停顿,只与GCroot数量有关。

最后的并发清理,此后没有任何存活对象了。

问题

这里要注意转发指针改变地址过程中,更新某个对象的字段的问题。这个有点和指令交错的问题有点像,所以这里采用了cas来保证成功实现。但是转发指针每一次使用都会产生一次额外的引用开销。

2.读屏障的过度使用

读屏障由于过于频繁,所以会堆积大量的操作,这里也是不希望有任何重量级的操作,所以后期优化过程中,将内存屏障模型改为引用访问屏障,只拦截对象中数据类型为引用类型的读写操作,而不会去管原生数据类型等其他非引用字段的读写。

成果

最大停顿时间没有实现10ms以内,但是却也远远优于G1,CMS,PS了。因此是低延迟的垃圾回收器。但是吞吐量却下降了。

ZGC

ZGC的目标是希望在对吞吐量影响不大的目标下,还能进一步优化低延迟的回收。

这里说一下它的不同点。堆内存布局,这回不仅仅是面向region了,还从灵活到具体,为region分配了不同大小,例如2MB的,32MB的,小型中型region,还有大小不定的大region。

        那么它的核心问题:并发算法是如何实现的呢。这里与Shenandoah的转发指针不同且牛逼的是,它采用了染色指针的技术。如果在对象头中存储信息,例如哈希码,锁记录等这样的,但是如果对象存在被移动的可能性,又如何能够访问成功呢。而ZGC的染色指针直接最存粹,最直接,直接把标记信息放到了引用对象的指针上了。

        linux下,尽管64位指针的高18位不能用来寻址,但剩余的46位指针所能支持的依然可以满足大部分服务器的需要。所以ZGC把高四位提取出来存储4个标志信息。通过标志位虚拟机就能看到其引用对象的三色标记状态。

染色指针

        染色指针可以使得一旦某个region存活的对象被移走之后,这个region就能够被立刻释放掉和重用掉,而不必等待整个堆中所有指向该region的对象被修正后才能清理,这里可以与shen做一个横向对比。

        染色指针可以大幅减少在垃圾回收过程中内存屏障使用数量。这些信息直接维护在指针中,而且目前为止ZGc并未使用过写屏障,只是用读屏障。一部分是因为使用了染色指针技术,一部分是因为还没实现分代收集。

        但是要顺利应用染色指针还需要解决一个问题。java虚拟机只是一个进程,随意定义内存中指针的某几位数字,操作系统大哥是否支持呢,处理器大哥是否支持呢。这个问题在Solaris/SpARC平台就比较容易解决,因为他是支持虚拟地址掩码的,设置之后就可以直接忽略染色指针的标志位,有点类似于子网掩码了。但是在X86-64平台上却没有这样的能力,因此ZGC采用了虚拟内存映射技术。ZGC在Linux/x86-64上使用了多重地址映射将多个不同的虚拟内存地址映射到同一个物理内存地址上。这意味着ZGC在虚拟内存中看到的地址空间要比实际的堆内存容量更大。只需要将染色指针的标志位看成是分段符,将这些不同的地址段都映射到同一个物理内存空间就好了,这样多重映射后就可以用染色指针正常寻址了。

运行

它的阶段可以分为,并发标记,并发预备重分配,并发重分配,并发重映射。

并发标记阶段与G1,Shen不同的是,zgc的标记是在指针上而不是对象上,更新染色指针的mark0和marked1标志位。

并发预备重分配:这里并不会像G1做收益优先回收,反而是用范围更大的扫描成本去换取省去G1中记忆集的维护成本。因此ZGC重分配只决定了里面的存活对象会被复制到其他的region中,复制过后原本的region会释放。这个标记过程针对全堆。

并发重分配:核心。这个也是完成对旧对象到新对象得引用转换上。访问操作会被内存屏障截获,从region得转发表上转发到新复制得对象并且修正新引用得值。直接指向新对象。与shen相比,只有第一次访问会陷入转发,其他就不会影响了,因为它会自愈。

并发重映射:这个也不是迫切需要做得工作,因为染色指针有自愈能力,它可以慢慢得改变旧的引用定位到新的引用上面去。这样旧的转发表就可以释放掉了。

缺点

zgc能接收的对象分配速率实际上不是很高,因为zgc要准备对一个非常大的堆做垃圾回收并发收集,因为他是并行的,所以不回给用户感觉上的停顿,创造的大量对象由于来不及进入当此标记的收集范围,因此只能当作全部存活来看,这就是大量的浮动垃圾,如果告诉分配持续的话,一次完整的并发收集周期会很长,除了增大堆内存容量外,更根本的方法是希望引入分带收集。

成果

zgc目前虽然还在实验状态,但它的成果是非常显著的,在吞吐量方面,已经无线接近于PS,直接超越G1,在停顿时间延迟上面,直接控制在10ms以内,秒杀其他所有。相信它商业化之后会非常的牛逼。

好啦,如果大家喜欢的化,就点个赞吧,分享整理笔记不易,欢迎沟通交流

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/475928.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Springboot+Vue前后端分离的在线图书商城(书城)系统

项目介绍 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本图书商城管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据…

UE snap02 解析ASCII文本文件

UE snap02 解析ASCII文本文件 示例数据data.dat 11389477.2714892 3364559.73645693 0 11389471.5162524 3364567.8860295 0 11389471.5162524 3365813.09618369 0 11388329.6082659 3366184.85895869 0 11388320.4775297 3366197.78833087 0 11388270.6882384 3366214.84811…

OpenAI Sora文生视频模型技术报告中英全文

Video generation models as world simulators 视频生成模型作为世界模拟器 We explore large-scale training of generative models on video data. Specifically, we train text-conditional diffusion models jointly on videos and images of variable durations, resolu…

jQuery 元素操作

文章目录 1. jQuery 样式操作1.1 操作 css 方法1.2 设置类样式方法*案例--tab栏切换 1.3 类操作和className 区别 2. jQuery 效果2.1 显示隐藏效果2.2 滑动效果事件切换动画队列及其停止排队方法 3.3 淡入淡出效果利用渐进方式调整透明度*案例--高亮突出显示 3.4 自定义动画 an…

国务院办公厅发布:政府类网站网页设计规范(试行)

国务院办公厅于2019年12月发布了《政府类网站网页设计规范(试行)》。该规范的发布旨在统一政府类网站的设计风格和标准,提升政府网站的用户体验和可访问性,推动政府信息公开和服务的提升。 该规范涵盖了政府类网站的各个方面&…

Java IO流(超详细!)上篇

目录 一、File类1、操作文件和目录 二、I/O流概述1、按流向划分:输入流和输出流2、按处理单元划分:字节流和字符流3、按流的角色划分:节点流和处理流 三、字节流1、字节输出流基类:OutputStream2、字节输出流FileOutputStream类3、…

未来已来?国内10家AI大模型盘点(附体验网址)

名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 1、阿里云——通义千问2、科大讯飞——星火大模…

全局过滤器实现Jwt校验

从Session到Jwt 之前我写过一篇 什么是 httpsession : 理解HttpSession 在经典的那个登录场景中: 客户端第一次访问的时候 需要登录 登录成功之后 后面再次访问的时候 为了让服务器认识 这是已经登录成功的我 在session中存储的用户的信息。 现在我…

【leetcode】628.三个数的最大乘积

前言:剑指offer刷题系列 问题: 给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。 示例: 输入:nums [1,2,3] 输出:6思路1: 先去计算输入列表 nums …

蓝桥杯刷题(十三)

1.煤球数目 代码 cnt ans 0 start 1 a [] while cnt<100:ansstartstart 1t ansstartcnt1a.append(ans) print(sum(a))2.奖券数目 代码 def f(x)->bool:while x:if x%104:return Falsex//10return True ans 0 for i in range(10000,100000):if f(i):ans1 print(a…

鸿蒙实战开发:【国际化部件】

简介 国际化部件为应用提供了一系列国际化接口&#xff0c;包括&#xff1a;时间日期格式化、数字格式化、月份星期格式化、单复数、度量衡等相关接口。基于这些国际化接口&#xff0c;开发者可以设计并实现具有良好国际化能力的应用&#xff0c;从而可以高效、低成本的实现应…

(一)基于IDEA的JAVA基础4

注释文本&#xff0c;注释模版 单行注释://开头放在代码前面&#xff0c;对少部分。 多行注释:快捷方式ctrlshift/,对段落代码注 释。 文档注释:/**……**/&#xff0c;用于声明作者或创作时 间。 文档注释如何设置&#xff0c;首先找到File中…

[flask]flask的路由

路由的基本定义 路由就是一种映射关系。是绑定应用程序&#xff08;视图&#xff09;和url地址的一种一对一的映射关系&#xff01;在开发过程中&#xff0c;编写项目时所使用的路由往往是指代了框架/项目中用于完成路由功能的类&#xff0c;这个类一般就是路由类&#xff0c;…

vscode git图形化使用教程(一文秒懂,快速上手)

vscode git图形化使用教程&#xff08;一文秒懂&#xff0c;快速上手&#xff09; vscode 图形化 git  ​ 右键 checkout 功能&#xff0c;切换到当前分支 &#xff08;如果想要切换分支&#xff0c;必须要先上传当前更改的再切换分支&#xff0c;切换完分支后变为之前分支内…

网络编程3.21作业

1.现象 1.增 2.删 3.改 4.查 2.源码 #include<myhead.h>int callback(void* arg,int a,char **msgtext,char **msgheader){if(*(int *)arg0){for(int i0;i<a;i){printf("%s\t",*(msgheaderi));}printf("\n");}for(int i0;i<a;i){printf(&quo…

【Linux】深入了解Linux磁盘配额:限制用户磁盘空间的利器

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 前言 在多用户环境下管理磁盘空间是服务器管理中的一项重要任务。Linux提供了强大的磁盘配额功能&#xff0c;可以帮助管理员限制用户或组对文件系统…

3.21总结​.Codeforces 第 935 轮(第 3 组)(A~D)题解​

一.Codeforces 第 935 轮&#xff08;第 3 组&#xff09;(A~D)题解 (直接给原文了,我电脑的翻译就是一坨.......) 这到题是一道思维题,理解题目意思就会觉得很简单首先内向的人肯定是一个人一个帐篷,我们只要关心外向的人就可以了,我们只要分外向的人可不可以被3整除,分成两种…

找不到msvcp110.dll无法继续执行程序的多种解决方法

在计算机操作系统中&#xff0c;msvcp110.dll文件扮演着至关重要的角色。作为Microsoft Visual C Redistributable Package的一部分&#xff0c;这个特定的dll文件包含了系统运行多种应用程序所必需的关键运行时库函数。许多Windows应用程序依赖于msvcp110.dll文件来执行基本操…

畅谈AIGC,ISIG-AIGC技术与应用发展峰会成功举办

3月16日&#xff0c;第四届ISIG中国产业智能大会在上海中庚聚龙酒店如期开幕&#xff0c;此次大会由苏州市金融科技协会指导、企智未来科技&#xff08;RPA中国、LowCode低码时代、AIGC开放社区&#xff09;主办。大会聚集了来自不同领域的专家学者、行业领军人物及技术研发者&…

亚信安慧AntDB:数字化创新背后的数据力量

亚信安慧AntDB的“融合实时”的特性&#xff0c;不仅使得数据库具备了更强大的适应性&#xff0c;更让企业在不同业务场景下能够更好地实现业务目标&#xff0c;释放出更大的商业价值。融合实时的特性让AntDB具有了高度灵活性和实时性&#xff0c;使其能够满足企业在不同业务需…