从JDK9到19,认识一个新的Java形态(内存篇)|全球信息
在JDK9之前,Java基本上平均每三年出一个版本。但是自从2017年9月份推出JDK9到现在,Java开始了疯狂更新的模式,基本上保持了每年两个大版本的节奏。从2017年至今,已经发布了一个版本到了JDK19。其中包括了两个LTS版本(JDK11与JDK17)。除了版本更新节奏明显加快之外,JDK也围绕着云原生场景的能力,推出并增强了一系列诸如容器内资源动态感知、无停顿GC(ZGC、Shenandoah)、运维等等云原生场景方面的能力。这篇文章是EDAS团队的同学在服务客户的过程中,从云原生的角度将相关的功能进行整理和提炼而来。希望能和大家一起认识一个新的Java形态。
上一篇(《从JDK9到19,我们帮您提炼了和云原生场景有关的能力列表(上)》)我们讲了在整个演进过程中针对运行时模型和运维能力的一些重要变化,这一节我们主要是来讲讲内存相关的变化。
JVMGC发展回顾
(相关资料图)
JVM自从诞生以来,以“内存自动管理”和“一次编译到处运行”两个杀手锏能力,外加Spring这个超级生态,在企业应用开发领域中一直处于“人人模仿,从未超越”的江湖地位。内存的自动管理从技术角度,用一句通俗的语言进行简述就是:“根据设计好的堆内存布局模型,采用一定的跟踪识别与清理的算法,达到内存自动整理及回收的效果”。而一代代内存管理技术不断演进的目标,就是在不断提升并发与降低延时的同时,寻找资源利用最优的方案,从某种意义上说,如果我们不带来一些突破性的算法,这个三者的关系如同分布式中的CAP定理一样,很难兼得。如下图所示:
在JVM中,内存管理趋近等同于GC,GC也是Java程序员获得一份工作时必考的知识点。其中CMS从1.4版本(2002年)开始引入,一度成为最为经典的GC算法。然而从JDK9开始发起弃用CMS的JEP提案,到2020年初发布的JDK14完全从代码中抹除,意味着在他成年之际正式宣告了他历史使命的结束。那么到现在我们又应该从什么角度上去理解这一技术领域的发展方向,今后面试官又会从哪些方面对我们发起提问,是不管技术如何演进,能确定的是变化主线是围绕着三个方向进行,分别是:堆内存布局、线程模型、收集行为。EDAS团队经过一段时间整理出来了这篇文章,我们也将从三个点出发进行分享,希望能给大家一些启发。
堆内存布局的变化
JVM堆内存布局最为经典的是分代模型,即年轻代和老年代进行区分,不同的区域采用的回收算法和策略也完全不一样。在一个在线应用(如微服务形态)的request<->response模型中,所产生的对象(Object)绝大多数是瞬时存活的对象,所以大部分的对象在年轻代就会被相对简单、轻量、且高频的MinorGC所回收。在年轻代中经过几次MinorGC若依然存活则会将其晋升到老年代。在老年代中,相比较而言由于对象存活多、内存容量大,所以所需要的GC时间相对也会很长,同时由于每一次的回收会伴随着长时间的Stop-The-World(简称STW)出现。在内存需求比较大且对于时延和吞吐要求很高的应用中,其老年代的表现就会显得捉襟见肘。而且由于不同的分代所采用的回收算法一般都不一样,随着业务复杂度的增加,GC行为变得越来越难以理解,调优处理也就愈发的复杂。
单纯从堆内存布局来理解,一个简单的逻辑是内存区域越小,回收效率越高,经典分代模型中的Young区已经印证了这一点。为了解决上述问题,G1算法横空出世,引出基于区域(Region)的布局模型,带来的变化是内存在物理上不再根据对象的“年龄”来划分布局,而是默认全部划分成等大小的Region和专门用来管理超级大对象的独占Region,年轻代和老年代不再是一个物理划分,只是一个Region的一个属性。直观理解上,除了能管理的内存更大(G1理论值64G)之外,这样带来一个显而易见的好处就是可以预控制一次FullGC的STW的时间,因为Region大小一致,则可以根据停顿时间来推算这次GC需要回收的Region个数,而没有必要每次都将所有的Region全部清理完毕。
随着这项技术的进一步发展,到了现代化的Pauseless(ZGC)的算法场景中,有些算法暂时没有了分代的概念,同时Region按照大小划分了Small/Medium/Large三个等级,更精细的Region管理,也进一步来更少的内存碎片和内存利用率的提升、及其STW停顿时间更精准的预测与管理。
线程模型变化
说线程模型之前,先简单提一下GC线程与业务线程,GC线程是指JVM专门用来处理GC相关任务的线程,这在JVM启动时就已经决定。在传统的串行算法中,是指只有一个GC线程在工作。在并行(Parallel)的算法中,存在多个GC线程一起工作的情况(CMS中GC线程个数默认是CPU的核数)。同时一些算法的某些阶段中(如:CMS的并发标记阶段),GC线程也可以和业务线程一起工作;这个机制就缩短了整体STW的时间,这也是我们所说的并发(Concurrent)模式。
在现代化的GC算法中,并不是所有和GC相关的任务都只能由GC线程完成,如ZGC中的Remap阶段,业务线程可以通过内存读屏障(ReadBarrier),来矫正对象在此阶段因为被重新分配到新区域后的指针变化,进而进一步减少STW的时间。
收集行为变化
收集行为是指的在识别出需要被收集的对象之后,JVM对于对象和所在内存区域如何进行处理的行为。从早期版本至今,大致分为以下几个阶段:
MarkCopy:是指直接将存活对象从原来的区域拷贝至另外一个区域。这是一种典型的空间换时间的策略,好处显而易见:算法简单、停顿时间短、且调参优化容易;但同时也带来了近乎一倍的空间闲置。在早期的GC算法使用的是经典的分代模型。其中对于年轻代Survivor区的收集行为便是这种策略。
MarkSweep:为了减少空间成倍的浪费,其中一个策略就是在原有的区域直接对对象Mark后进行擦除。但由于是在原来的内存区域直接进行对象的擦除,应用进程运行久了之后,会带来很多的内存碎片,其结果是内存持续增长,但真实利用率趋低。
MarkSweep-Compact:这是对于MarkSweep的一个改良行为,即擦除之后会对内存进行重新的压缩整理,用以减少碎片从而提升内存利用率。但是如果每次都进行整理,就会延长每次FullGC后的STW时间。所以CMS的策略是通过一个开关(-XX:+UseCMSCompactAtFullCollection默认开)和一个计数器(-XX:CMSFullGCsBeforeCompaction默认值为0)进行控制,表示FullGC是否需要做压缩,以及在多少次FullGC之后再做压缩。这个两个配置配合业务形态去做调优能起到很好的效果。
MarkSweep-Compact-Free:JVM的应用有一个“内存吞噬器”的恶名,原因之一就是在进程运行起来之后,他只会向操作系统要内存从来不会归还(典型只借不还的渣男)。不过这些在现代化的分区模型算法中开始有了改善,这些算法在FullGC之后,可以将整理之后的内存以区域(Region)为粒度归还给操作系统,从而降低这一个进程的资源水位,以此来提升整个宿主机的资源利用率。
对于云原生场景云原生的内在推动力之一是让我们的业务工作负载最大化的利用云所带来的技术红利,云带来最大的技术红利就是通过弹性等相关技术,带来我们资源的高效交付和利用,从而降低最终的经济成本。所以如何最大化的利用资源的弹性能力是很多技术产品所追求的其中一个目标。
这一小节,我们抽取了JDK9-JDK19中内存相关的代表性能力,分别是:G1NUMA-Aware、ElasticMetaspace、ZGCUncommitUnusedMemory。和大家一起感受一下JVM在新的技术趋势下如何拥抱和改变。
JEP345:G1NUMA-Aware
现代化的服务器大多是属于多Node的架构,下图表示有4个Node,每一个Node内部都会有相应的CPU(有的架构会有多个CPU)和对应的物理内存条。当CPU访问访问本Node内部的物理内存进行“本地访问”时,其速度是通过QPI访问其他节点内存时的速度接近两倍,同时不同远近Node的访问速度也都不一样。在开启NUMA的情况下,每个Node内的CPU将优先使用同Node内的“本地”内存,否则系统将所有Node内的内存统一对待进行随机分配和访问。
既然Numa的作用是CPU将尽量访问“本地”内存以加速内存访问速度,常规场景下如果我们需要使用这个能力,在系统开启Numa的前提下,我们还需要对运行的程序进行绑核调优等操作,以将应用程序运行的进程和CPU有一个绑定关系。要达到这一效果,除了系统提供了一些运维管理工具(如linux中的taskset命令)之外,程序也可以通过调用系统API(如linux中的pthread_setaffinity)。在JVM多线程的模型中,如果想要通过自动编程的方式来进行CPU绑定,当下只能选择带有特定能力的商业版本,在OpenJDK中还不能很方便的完成这一能力。
那JVM内对于Numa能做什么呢?这里有一个假设,在一个线程内运行的对象大部分都是瞬时的(即这个对象的作用域跟随创建它的线程(或Runnable)的运行结束而消亡),原因和我们在上面介绍堆内存布局模型时的新生代的选择是一样逻辑。基于这个假设,JVM主要聚焦在了解决新生代的内存分配和访问的Numa感知上。其实JVM对于Numa的支持很多年前就开始了,在YoungGC的并行(Parrallel)收集器(通过-XX:+UseParallelGC开启)中。开启Numa之后,JVM优先选择Node内部的“本地”内存进行新对象的创建。
在云原生场景下,一个Kubernetes集群通常托管高规格的机器、同时高密的部署的小规格的工作负载,这个场景下,一个工作负载一直运行在同一个CPU或固定几个CPU的场景会变得越来越普遍。如果JVM再把整个Worker的内存不加区分的对待并进行分配,我们的内存访问性能势必会急剧下跌。如下图所示:
G1算法通过JEP345在JDK14中得到了这一能力的支持,可通过参数-XX:+UseNUMA开启,开启之后,G1会尽量将固定大小的各个Region均摊在所有能分配的CPUNode中,在分配新对象时,将优先使用同一Node内的“本地”内存的Region,如果“本地”内存Region不够时,将对此Region触发一次GC;如果还不够,再按照CPU的远近尽量获取相邻Node的Region。此策略只针对G1中新生代的内存区域生效。老年代区域和大对象区域还是沿用默认的策略。
JEP387:ElasticMetaspace
Metaspace是用来存储JVM中类的元数据信息,包括类中的运行时数据结构、类中使用到的成员以及方法信息。他的前身是永久代,也就是PermGen。这一变化是JDK8中重要的一个升级的能力之一。从JEP122中提议并落地。这个JEP带来的具体的变化可以参考下图:
标签:
为您推荐
广告
- 从JDK9到19,认识一个新的Java形态(内存篇)|全球信息
- 蕃茄田美术中心|环球资讯
- 300本最前沿的Python编程电子书免费下载PDF版,从0基础到进阶的优秀书籍,拿走不谢
- 速讯:小米公布摄像模组新专利,摄像头可层叠组合
- 西安乐业卡补贴可用店铺 天天资讯
- 全球微头条丨大明嫔妃之离间
- 学插花、赏茶道、品徽菜…… 外籍女性友人在合肥感受“徽州韵味”-全球报资讯
- 9岁男孩得甲流,发烧1天后肺部大片变白!防治流感,这省官方推荐连花清瘟等药物
- 鹅的步态更是什么了_鹅式步伐_天天快看点
- 天天微头条丨3月9日基金净值:银河和美生活混合A最新净值1.7904,跌0.58%
- 天天通讯!协鑫新能源:预期2022年财年录得净亏损不少于13亿元,同比亏损扩大
- 【天天快播报】615公里!我国首次实现开放式架构双场量子密钥分发
- clean是什么意思_class是什么意思
- 空调异味如何彻底解决汽车(空调异味)-世界关注
- 全球热头条丨绿萝可以放卧室里吗 绿萝能不能放卧室里养护
- 环球影城里的变形金刚里面有人吗_北京环球影城的变形金刚里面是真人吗
- 当前速讯:韩国拟取消公共交通工具口罩令,仅医疗机构、药店等场所执行
- 连江:挖掘投资潜力 打造产业集群-全球简讯
- 全球视点!不该让你等太久粤语名字_不该让你等太久
- 世界快资讯:港股医药股走高
广告
- 全球百事通!知其不可而为之虽千万人吾往矣什么意思_虽千万人吾往矣什么意思
- 二维码打印机碳带怎么安装视频_二维码打印机
- 天天日报丨宁夏的全称是什么
- 环球短讯!「群众关切的教育事」湖南:一张高铁票背后的冷与热
- 无锡新吴:空港春韵 环球速看料
- 天天动态:拒绝报价!梅西续约or离队万众瞩目,巴黎要求降薪,球王或离队
- [综漫]玩转世界
- 23锦州银行CD035今日发布发行公告_每日动态
- 今日视点:昔日仇敌谈穆帅红牌:他在以此转移视线,但罗马每年为他付800万
- 2011中国国际妇幼婴童产业展览会暨轻工精品展览会 世界快消息
- 原神:别被骗了!新手到底抽迪希雅还是赛诺?谁是下水道和幻神?
- 辽宁双加时惜败山东 陶汉林27+9郭艾伦23+7+8
- super amoled和amoled的区别
- 当前观点:上海海事:船舶生活污水即使经处理达标,也不能向黄浦江排放
- 泸州至古蔺高速公路项目获省发改委批复
- 全球资讯:建筑业产值和建筑业增加值_建筑业增加值
- 收快递10次就能免费领礼品?这是骗局,别信!|天天日报
- 阴晴圆缺指什么生肖_阴晴圆缺
- 天天速讯:键盘快捷键使用大全_实用的键盘快捷键大全
- 布克谈杜兰特明日首秀:感到很兴奋 这是我们等待已久的时刻-天天滚动