jvm性能调优实战 - 27亿级数据量的实时分析引擎,为啥频繁发生Full GC

本文涉及的产品
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: jvm性能调优实战 - 27亿级数据量的实时分析引擎,为啥频繁发生Full GC

Pre

这个案例将会给大家分析一个频繁Full GC的真实生产案例,我们会延续之前讲过的一个案例,继续进行分析,下面先把之前的案例贴出来放在下文。

先给大家说一下这个系统的案例背景,大概来说是一个数据计算系统,日处理数据量在上亿的规模。

为了方便大家集中注意力理解这个系统的生产环境的JVM相关的东西,所以对系统本身就简化说明了。

简单来说,这个系统就是会不停的从MySQL数据库以及其他数据源里提取大量的数据加载到自己的JVM内存里来进行计算处理,如下图所示。

这个数据计算系统会不停的通过SQL语句和其他方式,从各种数据存储中提取数据到内存中来进行计算,大致当时的生产负载是每分钟大概需要执行500次数据提取和计算的任务。

但是这是一套分布式运行的系统,所以生产环境部署了多台机器,每台机器大概每分钟负责执行100次数据提取和计算的任务。

每次会提取大概1万条左右的数据到内存里来计算,平均每次计算大概需要耗费10秒左右的时间,然后每台机器是4核8G的配置,JVM内存给了4G,其中新生代和老年代分别是1.5G的内存空间,大家看下图。


新生代多久会塞满

现在明确了一些核心数据,接着我们来看看这个系统到底多快会塞满新生代的内存空间?

既然这个系统每台机器上部署的实例,每分钟会执行100次数据计算任务,每次是1万条数据需要计算10秒的时间,那么我们来看看每次1万条数据大概会占用多大的内存空间?

这里每条数据都是比较大的,大概每条数据包含了平均20个字段,可以认为平均每条数据在1KB左右的大小。那么每次计算任务的1万条数据就对应了10MB的大小。

所以大家此时可以思考一下,如果新生代是按照8:1:1的比例来分配Eden和两块Survivor的区域,那么大体上来说,Eden区就是1.2GB,每块Survivor区域在100MB左右,如下图。

基本上按照这个内存大小而言,大家会发现,每次执行一个计算任务,就会在Eden区里分配10MB左右的对象,那么一分钟大概对应100次计算任务,其实基本上一分钟过后,Eden区里就全是对象,基本就全满了。

所以说, 新生代里的Eden区,基本上1分钟左右就迅速填满了。


触发Minor GC的时候会有多少对象进入老年代?

此时假设新生代的Eden区在1分钟过后都塞满对象了,然后在接着继续执行计算任务的时候,势必会导致需要进行Minor GC回收一部分的垃圾对象。

那么上篇文章给大家讲过这里在执行Minor GC之前会先进行的检查。

首先第一步,先看看老年代的可用内存空间是否大于新生代全部对象?看下图,此时老年代是空的,大概有1.5G的可用内存空间,新生代的Eden区大概算他有1.2G的对象好了。

此时会发现老年代的可用内存空间有1.5GB,新生代的对象总共有1.2GB,即使一次Minor GC过后,全部对象都存活,老年代也能放的下的,那么此时就会直接执行Minor GC了。

那么此时Eden区里有多少对象还是存活的,无法被垃圾回收呢?

大家可以考虑一下之前说的那个点,每个计算任务1万条数据需要计算10秒钟,所以假设此时80个计算任务都执行结束了,但是还有20个计算任务共计200MB的数据还在计算中

那么此时就是200MB的对象是存活的,不能被垃圾回收掉,然后有1GB的对象是可以垃圾回收的,大家看下图。

此时一次Minor GC就会回收掉1GB的对象,然后200MB的对象能放入Survivor区吗?

**不能!**因为任何一块Survivor区实际上就100MB的空间,此时就会通过空间担保机制,让这200MB对象直接进入老年代去,占用里面200MB内存空间,然后Eden区就清空了,大家看下图。


系统运行多久,老年代大概就会填满?

那么大家想一下,这个系统大概运行多久,老年代会填满呢?

按照上述计算,每分钟都是一个轮回,大概算下来是每分钟都会把新生代的Eden区填满,然后触发一次Minor GC,然后大概都会有200MB左右的数据进入老年代。

那么大家可以想一下,假设现在2分钟运行过去了,此时老年代已经有400MB内存被占用了,只有1.1GB的内存可用,此时如果第3分钟运行完毕,又要进行Minor GC会做什么检查呢?

此时会先检查老年代可用空间是否大于新生代全部对象!

此时老年代可用空间1.1GB,新生代对象有1.2GB,那么此时假设一次Minor GC过后新生代对象全部存活,老年代是放不下的,那么此时就得看看一个参数是否打开了 。

如果“-XX:-HandlePromotionFailure”参数被打开了,当然一般都会打开其实,此时会进入第二步检查,就是看看老年代可用空间是否大于历次Minor GC过后进入老年代的对象的平均大小。

我们已经计算过了,大概每分钟会执行一次Minor GC,每次大概200MB对象会进入老年代。

那么此时发现老年代的1.1GB空间,是大于每次Minor GC后平均200MB对象进入老年代的大小的,所以基本可以推测,本次Minor GC后大概率还是有200MB对象进入老年代,1.1G可用空间是足够的。

所以此时就会放心执行一次Minor GC,然后又是200MB对象进入老年代。

转折点大概在运行了7分钟过后,7次Minor GC执行过后,大概1.4G对象进入老年代,老年代剩余空间就不到100MB了,几乎快满了,如下图。


系统运行多久,老年代会触发1次Full GC?

大概在第8分钟运行结束的时候,新生代又满了,执行Minor GC之前进行检查,此时发现老年代只有100MB内存空间了,比之前每次Minor GC后进入老年代的200MB对象要小,此时就会直接触发一次Full GC。

Full GC会把老年代的垃圾对象都给回收了,假设此时老年代被占据的1.4G空间里,全部都是可以回收的对象,那么此时一次性就会把这些对象都给回收了,如下图。

然后接着就会执行Minor GC,此时Eden区情况,200MB对象再次进入老年代,之前的Full GC就是为这些新生代本次Minor GC要进入老年代的对象准备的,如下图。

按照这个运行模型,基本上平均就是七八分钟一次Full GC,这个频率就相当高了。

因为每次Full GC速度都是很慢的,性能很差


该案例应该如何进行JVM优化?

相信通过这个案例,大家结合图一路看下来,对新生代和老年代如何配合使用,然后什么情况下触发Minor GC和Full GC,什么情况下会导致频繁的Minor GC和Full GC,大家都有了更加深层次和透彻的理解了。

对这个系统,其实要优化也是很简单的,因为这个系统是数据计算系统,每次Minor GC的时候,必然会有一批数据没计算完毕,但是按照现有的内存模型,最大的问题,其实就是每次Survivor区域放不下存活对象。

所以当时我们就是对生产系统进行了调整,增加了新生代的内存比例,3GB左右的堆内存,其中2GB分配给新生代,1GB留给老年代,这样Survivor区大概就是200MB,每次刚好能放得下Minor GC过后存活的对象了,如下图所示。

只要每次Minor GC过后200MB存活对象可以放Survivor区域,那么等下一次Minor GC的时候,这个Survivor区的对象对应的计算任务早就结束了,都是可以回收的

此时比如Eden区里1.6GB空间被占满了,然后Survivor1区里有200MB上一轮 Minor GC后存活的对象,如下图。

然后此时执行Minor GC,就会把Eden区里1.4GB对象回收掉,Survivor1区里的200MB对象也会回收掉,然后Eden区里剩余的200MB存活对象会放入Survivor2区里,如下图。

以此类推,基本上就很少对象会进入老年代中,老年代里的对象也不会太多的。

通过这个分析和优化,定时我们成功的把生产系统的老年代Full GC的频率从几分钟一次降低到了几个小时一次,大幅度提升了系统的性能,避免了频繁Full GC对系统运行的影响。


如果该系统的工作负载再次扩大10倍呢?

相信大家之前都看过这个案例了,这次正好借着这个机会再次重看一遍,加深一下印象,同时我们接着说当时那个生产系统在每日处理1亿数据之后,随着一段时间过后,工作负载再次扩大10倍的情景。

如果工作负载扩大10倍,那么大家参照上图来看,此时会导致每秒钟要加载100MB的数据到内存里去,对于1.6G的Eden而言,10多秒就会迅速塞满,此时就会触发Young GC。

但是之前说过,你每次加载一批数据到内存里去,一般要处理10秒以上的时间才能计算完毕,在计算完毕之前这些数据是不能被回收的。

所以如果你10多秒就触发一次Young GC,直接导致的后果就是,此时可能能回收掉的垃圾也就几百MB而言,可能1GB的对象都是无法回收的,大家仔细理解一下这个意思。

此时就会导致每隔10多秒,就有1GB的数据进入老年代中,而老年代之前给大家说过,也就1GB左右的空间而已,即使勉强让你放下了,那么下一次过10多秒之后,又会放1GB的对象到老年代,此时必然会提前触发Full GC去回收老年代里的1GB的对象,然后再让你把这次Young GC后存活的1GB对象放入老年代。

这就是当时我们遇到的真实生产场景,基本上一台4核8G的机器,每分钟要触发二三次Full GC,对系统性能造成了巨大的影响,简直是可怕至极。


使用大内存机器来优化上述场景

所以但是针对这个问题,因为考虑是计算类的系统,也是非常的吃内存的,所以同样是更换成了每台机器都是16核32G的高配置机器

这样的话,Eden基本上空间会扩大10倍,比如有16GB。

那么此时按照每秒加载100MB的数据到内存里进行计算,要2分钟左右才会触发一次Young GC,因为降低了Young GC的频率,所以每次Young GC的时候存活对象大概也就几百MB而已,不会超过1GB。

当时给Survivor区域分配的是每个Survivor有2GB内存,所以每次Young GC过后的存活对象可以轻松放入Survivor区域中,不会进入老年代。这就完美的通过提升机器配置的方式,解决了频繁Young GC和Full GC的问题。

很多同学可能会提问了,那么针对大内存机器,需要用G1来减少每次Young GC的停顿时间吗?

答案是:不用。因为这是一个后台自动进行计算的系统,他不是直接面向用户的系统,所以哪怕每隔2分钟一次Young GC,一次要停顿1秒钟,也对系统几乎没任何影响。


总结

这篇文章接着之前的案例,让大家看了一下,1亿数据量级下的系统部署4核8G的机器,Full GC为何频繁发生,如何优化?10亿量级下的系统部署在4核8G的机器上,Full GC会发生的有多么的恐怖,如何通过提升机器配置来优化?

相信大家仔细看完这个案例,多看几遍,透彻理解了,多频繁Full GC问题就彻底理解了。


思考

  • 看看你们线上系统一般每隔多长时间发生一次Full GC?
  • 每次Full GC持续多久?

对你们系统的性能有影响吗?


相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps 
相关文章
|
8天前
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
41 10
|
22天前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
79 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
24天前
|
存储 IDE Java
实战优化公司线上系统JVM:从基础到高级
【11月更文挑战第28天】Java虚拟机(JVM)是Java语言的核心组件,它使得Java程序能够实现“一次编写,到处运行”的跨平台特性。在现代应用程序中,JVM的性能和稳定性直接影响到系统的整体表现。本文将深入探讨JVM的基础知识、基本特点、定义、发展历史、主要概念、调试工具、内存管理、垃圾回收、性能调优等方面,并提供一个实际的问题demo,使用IntelliJ IDEA工具进行调试演示。
25 0
|
2月前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
60 2
|
2月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
68 3
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
59 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
2月前
|
算法 Java
JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
本文详细介绍了JVM中的GC算法,包括年轻代的复制算法和老年代的标记-整理算法。复制算法适用于年轻代,因其高效且能避免内存碎片;标记-整理算法则用于老年代,虽然效率较低,但能有效解决内存碎片问题。文章还解释了这两种算法的具体过程及其优缺点,并简要提及了其他GC算法。
 JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?