从 JDK 9 到 19,认识一个新的 Java 形态(内存篇)
阿里妹导读
这篇文章是 EDAS 团队的同学在服务客户的过程中,从云原生的角度将相关的功能进行整理和提炼而来。希望能和大家一起认识一个新的 Java 形态。
前言
在 JDK 9 之前,Java 基本上平均每三年出一个版本。但是自从 2017 年 9 月份推出 JDK9 到现在,Java 开始了疯狂更新的模式,基本上保持了每年两个大版本的节奏。从 2017 年至今,已经发布了一个版本到了 JDK 19。其中包括了两个 LTS 版本(JDK11 与 JDK17)。除了版本更新节奏明显加快之外,JDK 也围绕着云原生场景的能力,推出并增强了一系列诸如容器内资源动态感知、无停顿GC(ZGC、Shenandoah)、运维等等云原生场景方面的能力。这篇文章是 EDAS 团队的同学在服务客户的过程中,从云原生的角度将相关的功能进行整理和提炼而来。希望能和大家一起认识一个新的 Java 形态。 上一篇 (《 从 JDK 9 到 19,我们帮您提炼了和云原生场景有关的能力列表(上) 》)我们讲了在整个演进过程中针对运行时模型和运维能力的一些重要变化,这一节我们主要是来讲讲内存相关的变化。JVM GC 发展回顾
JVM 自从诞生以来,以 “内存自动管理” 和 “一次编译到处运行” 两个杀手锏能力,外加 Spring 这个超级生态,在企业应用开发领域中一直处于“人人模仿,从未超越”的江湖地位。内存的自动管理从技术角度,用一句通俗的语言进行简述就是:“根据设计好的堆内存布局模型,采用一定的跟踪识别与清理的算法,达到内存自动整理及回收的效果”。而一代代内存管理技术不断演进的目标,就是在不断提升并发与降低延时的同时,寻找资源利用最优的方案,从某种意义上说,如果我们不带来一些突破性的算法,这个三者的关系如同分布式中的 CAP 定理一样,很难兼得。如下图所示:- Mark Copy:是指直接将存活对象从原来的区域拷贝至另外一个区域。这是一种典型的空间换时间的策略,好处显而易见:算法简单、停顿时间短、且调参优化容易;但同时也带来了近乎一倍的空间闲置。在早期的 GC 算法使用的是经典的分代模型。其中对于年轻代 Survivor 区的收集行为便是这种策略。
- Mark Sweep:为了减少空间成倍的浪费,其中一个策略就是在原有的区域直接对对象 Mark 后进行擦除。但由于是在原来的内存区域直接进行对象的擦除,应用进程运行久了之后,会带来很多的内存碎片,其结果是内存持续增长,但真实利用率趋低。
- Mark Sweep-Compact: 这是对于 Mark Sweep 的一个改良行为,即擦除之后会对内存进行重新的压缩整理,用以减少碎片从而提升内存利用率。但是如果每次都进行整理,就会延长每次 FullGC 后的 STW 时间。所以 CMS 的策略是通过一个开关( -XX:+UseCMSCompactAtFullCollection 默认开) 和一个计数器( -XX:CMSFullGCsBeforeCompaction 默认值为 0) 进行控制,表示 FullGC 是否需要做压缩,以及在多少次 FullGC 之后再做压缩。这个两个配置配合业务形态去做调优能起到很好的效果。
- Mark Sweep-Compact-Free: JVM 的应用有一个“内存吞噬器”的恶名,原因之一就是在进程运行起来之后,他只会向操作系统要内存从来不会归还(典型只借不还的渣男)。不过这些在现代化的分区模型算法中开始有了改善,这些算法在 FullGC 之后,可以将整理之后的内存以区域(Region)为粒度归还给操作系统,从而降低这一个进程的资源水位,以此来提升整个宿主机的资源利用率。
云原生相关的重点能力
对于云原生场景云原生的内在推动力之一是让我们的业务工作负载最大化的利用云所带来的技术红利,云带来最大的技术红利就是通过弹性等相关技术,带来我们资源的高效交付和利用,从而降低最终的经济成本。所以如何最大化的利用资源的弹性能力是很多技术产品所追求的其中一个目标。 这一小节,我们抽取了 JDK9 - JDK 19 中内存相关的代表性能力,分别是:G1 NUMA-Aware、Elastic Metaspace、ZGC Uncommit Unused Memory。和大家一起感受一下 JVM 在新的技术趋势下如何拥抱和改变。 JEP 345: G1 NUMA-Aware 现代化的服务器大多是属于多 Node 的架构,下图表示有 4 个 Node,每一个 Node 内部都会有相应的 CPU(有的架构会有多个 CPU) 和对应的物理内存条。当 CPU 访问访问本 Node 内部的物理内存进行“本地访问”时,其速度是通过 QPI 访问其他节点内存时的速度接近两倍,同时不同远近 Node 的访问速度也都不一样。在开启 NUMA 的情况下,每个 Node 内的 CPU 将优先使用同 Node 内的“本地”内存,否则系统将所有 Node 内的内存统一对待进行随机分配和访问。-
存储信息调整:
将类中定义的常量和字符串常量池(Interned String)放入到了堆中,Metaspace 只存储类元数据信息,即:a) Klass 信息,描述类的基础属性和类的继承关系等;b)NonKlass 信息,包含方法、内部类信息、成员变量定义等。
- 内存布局调整: 与之前在堆中开辟一块区域相比,Metaspace 是直接使用操作系统的本地内存进行分配,本地内存划分成多个 Chunk,以 ClassLoader 为维度进行分配和管理。
当一个 ClassLoader 加载一个对象时,所需要的空间从空闲的 Chunk 中分配一个或多个固定大小的块,如未找到则向操作系统重新申请一个 Chunk。 当某一个 ClassLoader 中所有的类都被卸载的时候,就可以将它所引用的内存块都归还给 Chunk。 等到对应 Chunk 完全处于“空闲”状态的时候,这个 Chunk 也就就可以被操作系统回收。看到这里我们先暂停一下,思考两个问题:
-
他为什么这么调整?
从 JEP 的描述,只提到了因为需要和 JRockit (原 Oracle BEA JVM) 做融合,而 JRockit 的设计中并没有永久代。而从时间上看,正好是发生在 Oracle 收购 Sun 之后。所以一个猜想就是这个变化的根因应该是组织推动大于技术驱动。当然从技术上这样带来的好处也很显而易见:不再有负载的 Perm 设置;元空间和堆空间完全隔离后,两边的 GC 不会相互影响;单次 FullGC 因为扫描区域更小而使得 STW 时间更短;按照 Chunk 设计的构想,在类被卸载时,有助于 JVM 释放一些内存给操作系统等等。
-
有没有带来新问题?
有,就是在一些应用程序中会出现多种类频繁的 加载/卸载 的场景下, 导致 Metaspace 所管理的 Chunk 会不停的更新和释放而造成很严重的
内存碎片
,碎片整理机制的缺失导致理想中的效果并未达到。最终造成了更多的内存浪费。
- 首先:减少碎片,内存管理从内置的 Arena Chunk 内存管理算法,改为了简单且经典的伙伴算法,对,Linux 操作系统的内存管理就是基于伙伴算法的。
伙伴系统把所有的空闲页框分组为固定个数的块链表,每个块链表分别包含固定大小为 1K, 2K, 4K, .... 4M 大小的块。当应用程序向系统申请对应的内存大小时,系统将从最接近所需大小的链表中进行分配。
- 其次:按需使用,等到真正使用内存的时候才向操作系统发起内存申请,而不是一开始就申请出来一块很大的空间。
有一些 ClassLoader(如: BoostrapClassLoader)往往需要很多的空间,但是他真正使用并不是从一开始启动就需要,而且甚至是永远都不需要。
- 第三:增加策略,为了防止频繁的向操作系统 申请/释放 内存带来额外的系统开销,新引入了一个命令行参数 -XX:MetaspaceReclaimPolicy=(balanced|aggressive|none) 来进行调整。
其中 balance 是默认选项,会在系统回收和时间消耗之间做平衡,更多是兼容之前的行为。 aggressive 是一种最为 “激进” 的回收策略,通过在回收时降低对应页框大小至 16K(默认64K),使回收内存粒度更细来降低碎片。 而 none 则是关闭回收行为。JEP 351: ZGC Uncommit Unused Memory ZGC 在 JDK 11 时被引入,它是一款基于内存区域(Region) 布局的垃圾回收器,我们可以通过 -XX:+UseZGC 进行开启。作为一款主打 Pauseless 的现代化的收集器,ZGC 相比于 G1 除了提供了三个不同大小的 Region (2M/4M/8M,而 G1 为一个固定大小的值)进行管理之外,还因为在 GC 整理阶段提供了内存读屏障来矫正对象指针的技术使得最终的 STW 时间更短。但是在 JDK 14 之前,被清理的 Region 还是无法归还给操作系统,相比 G1 在 JDK9 中就提供了类似的能力滞后了两年多。 简单概述一下,这个 JEP 指的是每次 GC 结束,JVM 都会尝试将释放一部分内存归还给操作系统。但是如上一章节介绍 Elastic Metaspace 章节一样,频繁的向操作系统 申请/归还 只能带来更多的系统开销,如何取舍是一门艺术。那么该如何选择是否有操作手段呢?请先看下面这张图:
结语
到这里,所有相关的云原生场景解读就完了;整个读下来,自己的感觉是 JVM 在这个场景下除了更好的去融入到这个场景之外,而且还不断的摒弃自己原有的即使是很成熟经典的设计,层层蜕变,在云原生场景下不断的将技术价值释放出来。云原生潮涌来,我借用孙子兵法中的一句话与各位共勉:“故善战人之势,如转圆石于千仞之山者,势也。”。回到我们的场景通俗理解就是你会看到你的合作伙伴、你的客户、你所使用的工具链、甚至你找的下一份工作时面试官的问题,都在受到这一理念与技术的影响而做出改变,这是技术浪潮中的大势。 如对上述话题感兴趣想深入探讨,欢迎留言或加入钉群:21958624 与我们( EDAS 团队)进行沟通与交流,云原生微服务应用平台 EDAS 始终致力于应用架构云原生化,2023 春季新推采购活动,新客户头两月不限量使用,助力大家缩短转型过程中的路径。 写评测赢Airpods 2,OCR文档自学习评测上线!免费体验产品,写下你的使用体验,就有机会赢取Airpods 2、Modelscope卫衣、开发者评测定制鼠标垫、OCR文档自学习一万次/一千次资源包等豪礼!
【前20位】发布100字以上原创评测的用户可获得【30元天猫超市卡】,阳光普照,先到先得!
点击阅读原文查看详情, 写下真实感受!