后端开发和你聊聊 JVM 如何优化

news2024/12/23 15:59:29

作者:京东零售京麦研发 马万全

首先应该明确的是JVM调优不是常规手段,JVM的存在本身就是为了减轻开发对于内存管理的负担,当出现性能问题的时候第一时间考虑的是代码逻辑与设计方案,以及是否达到依赖中间件的瓶颈,最后才是针对JVM进行优化。

1.JVM内存模型

针对JAVA8的模型进行讨论,JVM的内存模型主要分为几个关键区域:堆、方法区、程序计数器、虚拟机栈和本地方法栈。堆内存进一步细分为年轻代、老年代,年轻代按其特性又分为E区,S1和S2区。关于内存模型的一些细节就不在这里讨论了,如下是从网上找的内存模型图:





接下来从内存模型简单流转来看一个对象的生命周期,对JVM的回收有一个概念,其中弱化堆栈和程序计数器

1.首先我们写的.java文件通过java编译器javac编译成.class文件

2.类被编译成.class文件后,通过类加载器(双亲委派模型)加载到JVM的元空间中

3.当创建对象时,JVM在堆内存中为对象分配空间,通常首先在年轻代的E区(这里只讨论在堆上分配的情况)

4.对象经历YGC后,如果存活移动到S区,多次存活后晋升到老年代

5.当对象不再被引用下一次GC,垃圾收集器会回收对象并释放其占用的内存。

1.1 年轻代回收原理

对象创建会在年轻代的E区分配内存,当失去引用后,变成垃圾存在E区中,随着程序运行E区不断创建对象,就会逐步塞满,这时候E区中绝大部分都是失去引用的垃圾对象,和一小部分正在运行中的线程产生的存活对象。这时候会触发YGC(Young Gc)回收年轻代。然后把存活对象都放入第一个S区域中,也就是S0区域,接着垃圾回收器就会直接回收掉E区里全部垃圾对象,在整个这个垃圾回收的过程中全程会进入Stop the Wold状态,系统代码全部停止运行,不允许创建新的对象。YGC结束后,系统继续运行,下一次如果E区满了,就会再次触发YGC,把E区和S0区里的存活对象转移到S1区里去,然后直接清空掉E区和S0区中的垃圾对象

1.2 、那么对象什么时候去老年代呢?

1.2.1、对象的年龄

躲过15次YGC之后的对象晋升到老年代,默认是15,这个值可以通过-XX:MaxTenuringThreshold设置

这个值设置的随意调整会有什么问题?

现在java项目普遍采用Spring框架管理对象的生命周期。Spring默认管理的对象都是单例的,这些对象是长期存活的应该直接放到老年代中,应该避免它们在年轻代中来回复制。调大晋升阀值会导致本该晋升的对象停留在年轻代中,造成频繁YGC。但是如果设置的过小会导致程序中稍微存在耗时的任务,就会导致大量对象晋升到老年代,导致老年代内存持续增长,不要盲目的调整晋升的阀值。

1.2.2、动态对象年龄判断

JVM都会检查S区中的对象,并记录下每个年龄段的对象总大小。如果某个年龄段及其之前所有年龄段的对象总大小超过了S区的一半,则从该年龄段开始的所有对象在下一次GC时都会被晋升到老年代。假设S区可以容纳100MB的数据。在进行一次YGC后,JVM统计出如下数据:

•年龄1的对象总共占用了10MB。

•年龄2的对象总共占用了20MB。

•年龄3的对象总共占用了30MB。

此时,年龄1至3的对象总共占用了60MB,超过了S区一半的容量(50MB)。根据动态对象年龄判断规则,所有年龄为3及以上的对象在下一次GC时都将被晋升到老年代,而不需要等到它们的年龄达到15。(注意:这里S区指的是S0或者S1的空间,而不是总的S,总的在这里是200MB)

这个机制使得JVM能够根据实际情况动态调整对象的晋升策略,从而优化垃圾收集的性能。通过这种方式,JVM尽量保持S区空间的有效利用,同时减少因年轻代对象过多而导致的频繁GC。

1.2.3.大对象直接进入老年代

如果对象的大小超过了预设的阈值(可以通过-XX:PretenureSizeThreshold参数设置),这个对象会直接在老年代分配,因为大对象在年轻代中经常会导致空间分配不连续,从而提早触发GC,避免在E区及两个S区之间来回复制,减少垃圾收集时的开销。

1.2.4.临时晋升

在某些情况下,如果S区不足以容纳一次YGC后的存活对象,这些对象也会被直接晋升到老年代,即使它们的年龄没有达到晋升的年龄阈值。这是一种应对空间不足的临时措施。

1.3老年代的GC触发时机

一旦老年代对象过多,就可能会触发FGC(Full GC),FGC必然会带着Old GC,也就是针对老年代的GC 而且一般会跟着一次YGC,也会触发永久代的GC,但具体触发条件和行为还取决于使用的垃圾收集器,文章的最后会简单的介绍下垃圾收集器。

Serial Old/Parallel Old

当老年代空间不足以分配新的对象时,会触发FGC,这包括清理整个堆空间,即年轻代和老年代。

CMS

当老年代的使用达到某个阈值(默认情况下是68%)时,开始执行CMS收集过程,尝试清理老年代空间。如果在CMS运行期间老年代空间不足以分配新的对象,可能会触发一次Full GC。 启动CMS的阈值参数:-XX:CMSInitiatingOccupancyFraction=75,-XX:+UseCMSInitiatingOccupancyOnly

G1

G1收集器将堆内存划分为多个区域(Region),包括年轻代和老年代区域。当老年代区域中的空间使用率达到一定比例(基于启发式方法或者显式配置的阈值)默认45%时,G1会计划并执行Mixed GC,这种GC包括选定的一些老年代区域和所有年轻代区域的垃圾收集。

Mixed GC的阈值参数-XX:InitiatingHeapOccupancyPercent=40,-XX:MaxGCPauseMillis=200

2.JVM优化调优目标:

2.1JVM调优指标

低延迟(Low Latency):GC停顿时间短。

高吞吐量(High Throughput):单位时间内能处理更多的工作量。更多的是CPU资源来执行应用代码,而非垃圾回收或其他系统任务。

大内存(Large Heap):支持更大的内存分配,可以存储更多的数据和对象。在处理大数据集或复杂应用时尤为重要,但大内存堆带来的挑战是GC会更加复杂和耗时。

但是不同目标在实现是本身时有冲突的,为什么难以同时满足?

低延迟 vs. 高吞吐量:要想减少GC的停顿时间,就需要频繁地进行垃圾回收,或者采用更复杂的并发GC算法,这将消耗更多的CPU资源,从而降低应用的吞吐量。

低延迟 vs. 大内存:大内存堆意味着GC需要管理和回收的对象更多,这使得实现低延迟的GC变得更加困难,因为GC算法需要更多时间来标记和清理不再使用的对象。

高吞吐量 vs. 大内存:虽然大内存可以让应用存储更多数据,减少内存管理的开销,但是当进行全堆GC时,大内存堆的回收过程会占用大量CPU资源,从而降低了应用的吞吐量。

2.2如何权衡

在实际应用中,根据应用的需求和特性,开发者和运维工程师需要在这三个目标之间做出权衡:

2.2.1Web应用和微服务 - 低延迟优先

场景描述:对于用户交互密集的Web应用和微服务,快速响应是提供良好用户体验的关键。在这些场景中,低延迟比高吞吐量更为重要。

推荐收集器:大内存应用推荐G1,内存偏小可以使用CMS,CMS曾经是低延迟应用的首选,因其并发回收特性而被广泛使用。不过由于CMS在JDK 9中被标记为废弃,并在后续版本中被移除可以使用极低延迟ZGC或Shenandoah。这两种收集器都设计为低延迟收集器,能够在大内存堆上提供几乎无停顿的垃圾回收,从而保证应用的响应速度,但是支持这两个回收器的JDK版本较高,在JDK8版本还是CMS和G1的天下。

2.2.2 大数据处理和科学计算 - 高吞吐量优先

场景描述:大数据处理和科学计算应用通常需要处理大量数据,对CPU资源的利用率要求极高。这类应用更注重于高吞吐量,以完成更多的数据处理任务,而不是每个任务的响应时间。

推荐收集器:Parallel GC。这是一种以高吞吐量为目标设计的收集器,通过多线程并行回收垃圾,以最大化应用吞吐量,非常适合CPU资源充足的环境。

2.2.3. 大型内存应用 - 大内存管理优先

场景描述:对于需要管理大量内存的应用,例如内存数据库和某些缓存系统,有效地管理大内存成为首要考虑的因素。这类应用需要垃圾回收器能够高效地处理大量的堆内存,同时保持合理的响应时间和吞吐量。

推荐收集器:G1 GC或ZGC。G1 GC通过将堆内存分割成多个区域来提高回收效率,适合大内存应用且提供了平衡的延迟和吞吐量。ZGC也适合大内存应用,提供极低的延迟,但可能需要对应用进行调优以实现最佳性能。

3.JVM优化一般是针对于两种场景

3.1新应用上线,通过预估核心接口流量进行压测,观察JVM的GC情况并调优

压测需要观察那些重要的指标呢

•YGC与FGC频率和耗时

•YGC过后多少对象存活

• 老年代的对象增长速率

通过jstat观察出来上述JVM运行指标!

3.2老应用通过监控收到JVM异常反馈,或者程序出现下列问题进行优化

3.2.1应用出现OutOfMemory等内存异常

(1)堆内存溢出 Java heap space

对象持续创建而不被回收或者来不及回收,导致堆内存耗尽。

超预期请求:面临突发的高并发请求或处理大量数据时,创建了大量线程和对象,GC回收后的空间,不足以放下存活的对象就会造成OOM。需要我们做好流量控制和预估,然后针对这种情况提前扩容或者限流。

内存泄漏:大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收,是否使用JDK线程池工具等,都是编码异常需要导出dump文件针对代码进行分析。

滥用缓存:本地缓存工具占用大量内存,导致堆使用空间变小,需要合理设置缓存大小以及超时时间

大量对象:再循环中创建大量对象导致堆内存被占满,避免在循环中创建对象。重复对象使用池化技术

大对象或大数组:创建超大数组,上传或者导出大文件,查询不带条件拖库,编码做好边界限制,有一个良好的编码习惯。

(2)元空间溢出 Metaspace

元空间的溢出通常是因为加载的 class 数目太多或体积太大

例如:动态生成大量Class对象,比如某些框架(如OSGi、ASM)动态生成大量的类,这些类占用的空间可能超过了元空间的限制,或者加载了大量的第三方库,这些库中包含的类和常量占用了大量的方法区空间。如果是正常类加载需要调大元空间-XX:MaxMetaspaceSize,否则需要导出DUMP文件,分析是否存在重复类

(3)虚拟机栈和本地方法栈溢出

线程请求的栈深度超过了虚拟机栈和本地方法栈允许的最大深度。这种情况通常发生在深度递归调用的情况下(-Xss参数设置栈的大小)。

应用创建了过多线程,超出了系统承载能力,尤其是在32位系统上,每个线程的栈空间(默认1MB)会占用一定的地址空间,可能会导致系统无法分配足够的地址空间给新的线程。

(4)直接内存溢出 Direct buffer memory

Java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序通过 Direct ByteBuffer 结合内存映射文件(Memory Mapped File)实现高速 IO。Direct ByteBuffer 的默认大小为 64 MB

•检查堆外内存使用代码,排查是否正确使用ByteBuffer.allocateDirect

•检查是否直接或间接使用了 NIO,如 netty,jetty 等。

•通过启动参数 -XX:MaxDirectMemorySize 调整 Direct ByteBuffer 的上限值。

3.2.2 Heap内存(老年代)持续上涨达到设置的最大内存值;

老年代持续上涨是JVM优化的重要指标,但是老年代持续上涨有多种原因

内存泄漏:最开始的表现也是老年代的持续上涨,触发FGC无法回收抛出OOM,系统宕机!

正常情况:可能是因为流量徒增导致年轻代处理不过来,临时移入老年代,执行FGC后内存明显下降!

大对象:大对象直接分配在老年代,触发FGC后内存明显下降!

年轻代的S区设置过小:E区正常回收后存活的对象,在S区放不下直接晋升到老年代,有一个大坑就是JAVA8默认收集器Parallel Scavenge为了处理更大的吞吐量会动态调整S区,在线上运行一段时间后S区会变得很小,导致大量对象进入到老年代,我在优化实战中排查过这个问题

3.2.3 FGC 次数频繁

频繁进行FGC如果出现OOM按照3.2.1进行排查

频繁FGC但是内存能被回收按照3.2.2进行排查

3.2.4 GC 停顿时间长

YGC停顿





可以看看这篇文章总结的挺好的:JVM性能调优--YGC

FGC停顿

FGC的触发一般是老年代或者元空间内存不足,FGC执行本身是比较耗时的操作,会回收整个堆内存以及元空间,我们在优化JVM尽量避免FGC,或者尽量少的FGC。FGC停顿指标需要结合FGC执行频率,以及历史执行时间来看如果是因为内存空间大导致回收慢可以选择G1针对大内存进行处理

总结:

JVM优化没有拿过来直接用的方案,所有好的JVM优化方案都是在当前应用背景下的,还是开头那句话 JVM调优不是常规手段,如果没有发现问题尽量不主动优化JVM,但是一定要了解应用的JVM运行情况,这时候好的监控就显得格外重要。

那么好的JVM应该是什么样的呢?简单的说就是尽量让每次YGC后的存活对象小于S区域的50%,都留存在年轻代里。尽量别让对象进入老年代。尽量减少FGC的频率,避免频繁FGC对JVM性能的影响

了解了JVM优化的基本原理之后,实战就需要在日常中积累了,墨菲定律我觉得在这个场景很适用,不要相信线上的机器是稳定的,如果观察到监控有异常,过一会可能恢复了就不了了之,要敢于去排查问题,未知的总是令人恐惧的,在排查的过程中会加深自己对JVM的理解的同时,也会对应用更有信心。

关注京东零售技术微信公众号,长按下方二维码后台回复“进群”加入技术交流群!与京东零售技术专家们切磋交流!

图片

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

16.1 微信支付

1. 概述 2. 微信支付时序图与流程 官方开发文档 Native支付文档 2.1 Native支付时序图 3. 订单表 单独开发一个支付平台,其他平台遇到支付业务需求时,可调用支付平台完成相应的支付业务 演示:平台有支付平台、商户平台 客户在商户平台发起Vip开通的支付请求,商户平台生成…

IDEA配置Tomcat,解决jsp页面显示问题(Please, configure Web Facet first!)和(There is no configured/running web-se)

在学校,大部分老师还是使用eclipse,他们使用的jsp页面,而我却想使用idea,来操纵jsp页面,可是为什么我将jsp放入项目当中,会出现报错,例如: 又或者: 哎呀,那怎…

数据结构与算法-二分搜索树遍历

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言一、二分搜…

Halcon 边缘提取(亚像素)

Halcon提供多种边缘提取算法。像素提取方法有常用的边缘提取算子或深度学习分割模型等。考虑到精度问题可能需要提取亚像素边缘。当然也可以提取轮廓:线、圆、椭圆等。本文只讨论提取轮廓。 1 基本概念 正常情况下,无需特殊操作即可提取边缘轮廓。 1…

这些天,有多少人去电影院看巴黎奥运会?

近期,巴黎奥运会的赛况频频登上热搜,中国奥运健儿们的奋勇拼搏令人热血沸腾。而许多观众,或由于租房情况不便于用投影仪观赏比赛,或由于期待更佳的观赛体验,因此有不少人去电影院观赛。由于实时数据的更新性和统计的复…

[Bugku] web-CTF靶场-计算器

计算器 1.开启环境 理论上来说题目最多只能填0-9的答案,如果需要填大于9的需要修改maxlength参数 得出flag

「码」上行动 一物一码+TPM让“多进货多卖货”不再是口号

“门店进货量翻倍,一些门店一个月连续补货3次!”在与纷享销客产品经理的电话交流中,X饮品企业的张总难掩喜悦之情。 他兴奋地表示,在北方城市推出的基于一物一码的门店进销双激励码营销试点取得了显著成果,所有参与试…

开放原子校源行 | 武汉大学师生一行走进麒麟信安,开展社会实践交流活动

“开放原子校源行”是开放原子开源基金会作为国家级开源公益平台发起的长期性开源教育推广公益项目。项目拟通过资助高校设立开源社团、推广开源课程、设置开源助学金、引导开源实践等方式培育开源人才,加快将开源文化、理念和技术融入校园,引导广大师生…

CST软件如何实现S-parameter随其他参数变化的1D曲线

本期的FAQ更新一个非常高频的问题,如何实现S-parameter随其他参数变化的1D曲线。有的时候也不一定是S参数,可能是用户关心的阻抗结果,也可能是VSWR等。 在老版本的时候,CST软件有个后处理的宏可以很容易的将某一频点下的0D转换成…

引领未来交通新纪元:综合智慧监管平台

引领未来交通新纪元:综合智慧监管平台 在21世纪的今天,随着科技的飞速发展,交通运输行业作为国民经济的命脉,正经历着前所未有的变革与挑战。面对日益增长的运输需求、复杂多变的交通环境以及公众对安全、高效、绿色出行的迫切期…

AcWingTrie树

字典树的应用背景: 看以下几个题: 1、给出n个单词和m个询问,每次询问一个单词,回答这个单词是否在单词表中出现过 答:简单!map,短小精悍。 好。下一个 每次询问一个前缀,回答询问是多少个单词的前缀。2、给出n个单词和…

硬核!288页Python核心知识笔记(附思维导图,建议收藏)

不少朋友在学习Python时,都会做大量的笔记,随着学习进度的增加,笔记越来越厚,但有效内容反而越来越少。 今天就给大家分享一份288页Python核心知识笔记,相较于部分朋友乱糟糟的笔记,这份笔记更够系统地总结…

软件项目管理工具排行榜:国内外优劣对比

项目管理工具对项目开发的重要性不言而喻,通过对比业内对各个项目管理工具的认可度,筛选出人们公认的软件项目管理工具排行榜,具体详细如下: 1、Microsoft Project 简介:Microsoft Project是一款强大的基于云的项目管理…

【数据结构】包装类和泛型

🎉欢迎大家收看,请多多支持🌹 🥰关注小哇,和我一起成长🚀个人主页🚀 ⭐在更专栏Java ⭐数据结构 ⭐已更专栏有C语言、计算机网络⭐ 👑目录 包装类🌙 ⭐基本类型对应的包…

4款一眼万年的精品软件,免费又好用,可惜很多人不知道

各位Windows用户,有没有觉得自带的播放器不太好用?曾经用过暴风影音、QQ影音、迅雷看看和快*等等国产播放器的大佬们,估计小孩都上小学了吧。 PotPlayer 这款PotPlayer视频播放器,它占用空间小,功能丰富强大&#xf…

【Qt】为什么Qt是你选择的理由?

目录 客户端 图形化界面 Windows环境下可以开发GUI的方案 Qt介绍 Qt的发展史 Qt所支持的平台 Qt版本 Qt的优点 Qt的应用场景 Qt的行业发展方向 Qt的发展前景 使用Qt的成功案例 一般来说,计算机的岗位有四种,分别是: 后端前端测试…

Golang通用代码生成器:仙童 电音仙女尝鲜版八,从MySQL8自动反射生成项目

Golang通用代码生成器:仙童 电音仙女尝鲜版八,从MySQL8自动反射生成项目 Golang通用代码生成器仙童,电音仙女尝鲜版八,此版本增强修复了数据库自动反射功能。更新修复了前端代码生成器无登录模块时的错误。修复了多对多候选的缺陷…

乐鑫原厂ESP32-S3模块ESP32-S3-WROOM-1区别与比较

乐鑫原厂ESP32-S3对外发售的模块,公开的有3类5种,分别是: 1,ESP32-S3-WROOM-1 模组 2,ESP32-S3-WROOM-1U 模组 3,ESP32-S3-WROOM-2 模组 4,ESP32-S3-MINI-1 模组 5,ESP32-S3-MINI-1…

Java对象流

对象流 对象输入流 java.io.ObjectInputStream使用对象流可以进行对象反序列化 构造器 ObjectInputStream(InputStream in) 将当前创建的对象输入流链接在指定的输入流上 方法 Object readObject() 进行对象反序列化并返回。该方法会从当前对象输入流链接的流中读取若干…

Python写UI自动化--playwright(在UI元素上悬停)

要在UI元素上执行鼠标悬停(hover)动作,可以使用page.hover()方法。这个方法使鼠标指针移动到指定的元素上,就像用户将鼠标悬停在该元素上一样。这对于触发那些依赖于鼠标悬停事件的行为(如显示下拉菜单、提示框或其他动…