jvm性能调优 - 06线上应用部署JVM实战_堆内存预估与设置

简介: jvm性能调优 - 06线上应用部署JVM实战_堆内存预估与设置

Pre

咱们先简单回顾一下 ,现在大家肯定都知道,我们平时代码里创建的对象,都是优先在新生代分配的

然后随着一些方法执行完毕,大部分新生代里的对象就没有人引用了,就成了垃圾对象,如下图所示:

大家可以想象一下,随着代码持续运行,新生代里对象会越来越多,而且里面大部分的对象其实都是那种短生存周期的对象,很快就没有人引用他们了,因此大部分都会是一些垃圾对象。

然后代码继续运行,是不是肯定会创建新的对象分配在新生代里?

肯定是的,所以一旦新生代里内存不够了,就会触发一次Minor GC,把新生代里那些没有人引用的垃圾对象都给回收掉,腾出来内存空间。如下图所示:

如果是那种在长周期存活的对象,他在新生代里会持续躲过多次垃圾回收,每躲过 一次垃圾回收,年龄会增长1岁

然后当他成为是十多岁的“老年人”的时候,就会被转移到老年代里去,如下图:

好,到此为止,我们撇开一些特殊情况,至少JVM中基本的内存分配原理,大家就搞清楚了

目前就是让大家屡清楚短生存周期的对象和长生存周期的对象分别是什么,他们是如何在新生代里分配的,新生代什么时候触发Minor GC,然后长生存周期的对象如何转移到老年代里去。


核心参数

在JVM内存分配中,有几个参数是比较核心的,如下所示。

  • -Xms:Java堆内存的大小
  • -Xmx:Java堆内存的最大大小
  • -Xmn:Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
  • -XX:PermSize:永久代大小
  • -XX:MaxPermSize:永久代最大大小
  • -Xss:每个线程的栈内存大小

下面我们对上述参数来进行一一说明。

-Xms和-Xmx,分别用于设置Java堆内存的刚开始的大小,以及允许扩张到的最大大小。

对于这对参数,通常来说,都会设置为完全一样的大小 。

这两个参数,是用来限定Java堆内存的总大小的,如下图。

-Xmn,这个参数也是很常见的,他用来设置Java堆内存中的新生代的大小,然后扣除新生代大小之后的剩余内存就是给老年代的内存大小,我们看下图:

-XX:PermSize和-XX:MaxPermSize,分别限定了永久代大小和永久代的最大大小

通常这两个数值也是设置为一样的。

如果是JDK 1.8以后的版本,那么这俩参数被替换为了-XX:MetaspaceSize和-XX:MaxMetaspaceSize,但是大家至少得知道,这两个参数限定了永久代的大小,如下图所示:

-Xss,这个参数限定了每个线程的栈内存大小

大家都很清楚,每个线程都有一个自己的虚拟机栈,然后每次执行一个方法,就会将方法的栈帧压入线程的栈里,方法执行完毕,那么栈帧就会从线程的栈里出栈,如下图:


如何在启动系统的时候设置JVM参数?

IDEA

在“VM arguments”中输入你的JVM参数即可

比如你可以按照下面的示例来设置,-Xms之类的参数直接后面跟上你要设置的内存大小,多少M即可。

但是-XX:PermSize这种格式的参数,需要跟一个“=”符号,跟上你要设置的内存大小即可。

-Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M 

那么如果是在线上部署系统应该如何设置JVM参数呢?

其实都很简单,比如说采用“java -jar”的方式启动一个jar包里的系统,那么就可以采用类似下面的格式:

java -Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M -jar App.jar

百万交易系统JVM设置案例

百万交易的支付系统案例

通过分析一个支付系统的核心业务流程,然后结合我们学习到的JVM相关的知识,来一步步探究,JVM内存相关的这些核心参数,到底在我们上线一个生产系统的时候,针对预估的并发压力,到底应该如何合理的给出一个未经过调优的比较合理的初始值。

另外我们会分析各种参数在设置的时候有哪些考虑的点,Java堆内存到底需要多大?新生代和老年代的内存分别需要多大?永久代和虚拟机栈分别 需要多大?这些我们都结合案例来一步一步的分析。

其实JVM参数到底该如何设置,一定是根据不同的业务系统他具体的一些场景来调整的,不是说有一个通用的配置和模板,照着设就没问题了,那个思路肯定是不对的,也不能干巴巴的告诉你,这个参数应该这样设置,那个参数应该那样设置。

一切都要从案例出发,结合业务场景来分析。

目的:我们通过一个案例来分析一下,教会大家自己负责的线上系统,到底该如何合理设置JVM内存大小。


业务

先来看看,如果在一个电商系统里,一个支付系统大概应该是一个什么样的位置,如下图。

大概的流程我想大家都网购过哈,流程都会非常的清晰,假设我们在一个APP或者一个网站里买东西,大体上都是对一些商品加到购物车里,然后下个订单,接着对订单进行支付,钱从我们的账户划拨到人家网站的账户里去,大致如此。

接着我们来讲一下支付的核心业务流程,大家先看下面的图。

通过上图标号序号的步骤,大家可以很清晰的知道这个流程了

  • 首先用户在我们的商城系统提交支付一个订单的请求,接着商城系统把这个请求提交给支付系统,支付系统就会生成一个支付订单,此时订单状态可能是“待支付”的状态。
  • 然后支付系统指引用户跳转到付款页面,选择一个付款方式
  • 然后用户发起实际支付请求,支付系统把实际支付请求转交给第三方支付渠道,比如微信或者支付宝,它们会去处理支付请求进行资金转移。
  • 如果微信或者支付宝处理完支付之后,就会返回支付结果给支付系统,支付系统可以更新自己本地的支付订单的状态变成“已完成”。

当然,其实一个完整的支付系统还包含很多东西。


系统的压力在哪里?

接着我们来考虑一下,一个每日百万交易的支付系统的压力到底集中在哪里呢?

比如上面的那个核心支付流程,我们的这套系统每日要发生百万次交易。

一般达到百万交易,要不然是国内最大的互联网公司,要不就是一个通用型第三方支付平台,对接各种APP的支付交易。

其实大家通过上图都能明显看到,上述业务流程中,最核心的环节,就是在用户发起支付请求的时候,会生成一个支付订单

这个支付订单需要记录清楚比如是谁发起支付?对哪个商品的支付?通过哪个渠道进行支付?还有发起支付的时间?等等,诸如此类的信息。

如果每日百万交易,那么大家可以想象一下,在我们的JVM的角度来看,就是每天会在JVM中创建上百万个支付订单对象

大家仔细想想,是不是这么回事?如下图:

所以我们的支付系统,其实他的压力有很多方面,包括高并发访问、高性能处理请求、大量的支付订单数据需要存储,等等技术难点。

但是抛开这些系统架构层面的东西,单单是在JVM层面,我们的支付系统最大的压力,就是每天JVM内存里会频繁的创建和销毁100万个支付订单,所以这里就牵扯到一个核心的问题。

  • 我们的支付系统需要部署多少台机器?
  • 每台机器需要多大的内存空间?
  • 每台机器上启动的JVM需要分配多大的堆内存空间?
  • 给JVM多大的内存空间才能保证可以支撑这么多的支付订单在内存里的创建,而不会导致内存不够直接崩溃?

这就是我们本文要考虑的核心问题。


每秒钟需要处理多少笔支付订单?

要解决线上系统最核心的一个参数,也就是JVM堆内存大小的合理设置,我们首先第一个要计算的,就是每秒钟我们的系统要处理多少笔支付订单。

假设每天100万个支付订单,那么一般用户交易行为都会发生在每天的高峰期,比如中午或者晚上。

假设每天高峰期大概是几个小时,用100万平均分配到几个小时里,那么大概是每秒100笔订单左右,咱们就以每秒100笔订单来计算一下好了。

假设我们的支付系统部署了3台机器,每台机器实际上每秒大概处理30笔订单。

大家看下面的图,这个图可以反映出来支付系统每秒钟的订单处理压力。


每个支付订单处理要耗时多久?

下一个问题,咱们必须要搞明白的一个事儿,就是每个支付订单大概要处理多长时间?

如果用户发起一次支付请求,那么支付需要在JVM中创建一个支付订单对象,填充进去数据,然后把这个支付订单写入数据库,还可能会处理一些其他的事情

咱们就假设一次支付请求的处理,包含一个支付订单的创建,大概需要1秒钟的时间。

那么大体上你的脑子里可以出现的一个流动的模型,应该是每台机器一秒钟接收到30笔支付订单的请求,然后在JVM的新生代里创建了30个支付订单的对象,做了写入数据库等处理

接着1秒之后,这30个支付订单就处理完毕,然后对这些支付订单对象的引用就回收了,这些订单在JVM的新生代里就是没人引用的垃圾对象了。

接着再是下一秒来30个支付订单,重复这个步骤。


每个支付订单大概需要多大的内存空间?

接着我们来计算一下,每个支付订单对象大概需要多大的内存空间?

之前的文章里有一个思考题, 已经说过这个怎么计算了,其实不考虑别的,你就直接根据支付订单类中的实例变量的类型来计算就可以了。

比如说支付订单类如下所示,你只要记住一个Integer类型的变量数据是4个字节,Long类型的变量数据是8个字节,还有别的类型的变量数据占据多少字节

百度一下都可以查到,然后就可以计算出每个支付订单对象大致占据多少字节。

一般来说,比如支付订单这种核心类,你就按20个实例变量来计算,然后一般大概一个对象也就在几百字节的样子

我们算他大一点好了,就算一个支付订单对象占据500字节的内存空间,不到1kb。

每秒发起的支付请求对内存的占用

之前说过,假设有3台机器,每秒钟处理30笔支付订单的请求,那么在这1秒内,大家都知道,肯定是有方法里的局部变量在引用这些支付订单的,如下图:

那么30个支付订单,大概占据的内存空间是30 * 500字节 = 15000字节,大概其实也就15kb而已。其实是非常非常小的。

让支付系统运行起来分析一下

现在我们已经把整个系统运行的关键环节的数据都分析清楚了,大家可以大致脑子里思考一下,每秒30个支付请求,创建30个支付订单对象,也就占据kb级别的内存空间而已

然后接着1秒过后,这30个对象就没有人引用了,就成了新生代里的垃圾了。

下一秒请求过来,我们的系统持续的创建支付订单对象,不停在新生代里放入30个支付订单,然后新生代里的对象会持续的累积和增加。

直到有一刻,发现可能新生代里都有几十万个对象了,此时占据了几百MB的空间了,可能新生代空间就快满了。

然后就会触发Minor GC,就把新生代里的垃圾对象都给回收掉了,腾出内存空间,然后继续来在内存里分配新的对象。

这就是这个业务系统的运行模型。


对完整的支付系统内存占用需要进行预估

之前的分析,全部都是基于一个核心业务流程中的一个支付订单对象来分析的,其实那只是一小部分而已。

真实的支付系统线上运行,肯定每秒会创建大量其他的对象,但是我们结合这个访问压力以及核心对象的内存占据,大致可以来估算一下整个支付系统每秒钟大致会占据多少内存空间。

其实如果你要估算的话,可以把之前的计算结果扩大10倍~20倍。也就是说,每秒钟除了在内存里创建支付订单对象,还会创建其他数十种对象。

那么每秒钟创建出来的被栈内存的局部变量引用的对象大致占据的内存空间就在几百KB~1MB之间。

然后下一秒继续来新的请求创建大概1MB的对象放在新生代里,接着变成垃圾,再来下一秒。

循环多次之后,新生代里垃圾太多,就会触发Minor GC回收掉这些垃圾。这就是一个完整系统的大致JVM层面的内存使用模型。


支付系统的JVM堆内存应该怎么设置?

其实结合支付系统的核心业务流程分析清楚了之后,大家就完全知道这么一个线上系统,每个机器上部署上线的时候,JVM的堆内存应该如何设置了。

其实一般来说这种线上业务系统,常见的机器配置是2核4G,或者是4核8G。

如果我们用2核4G的机器来部署,那么还是有点紧凑的,因为机器有4G内存,但是机器本身也要用一些内存空间,最后你的JVM进程最多就是2G内存

然后这2G还得分配给方法区、栈内存、堆内存几块区域,那么堆内存可能最多就是个1G多的内存空间。

然后堆内存还分为新生代和老年代,你的老年代总需要放置系统的一些长期存活的对象吧,怎么也得给几百MB的内存空间,那么新生代可能也就几百MB的内存了。

这样的话,大家可以看到,我们上述的核心业务流程,只不过仅仅是针对一个支付订单对象来分析的,但是实际上如果扩大10倍~20倍换成对完整系统的预估之后,我们看到,大致每秒会占据1MB左右的内存空间。

那么如果你新生代就几百MB的内存空间,是不是会导致运行几百秒之后,新生代内存空间就满了?此时是不是就得触发Minor GC了?

其实如果这么频繁的触发Minor GC,会影响线上系统的性能稳定性,具体原因后续再说。

这里大家首先要明白的一点,就是频繁触发GC一定不是什么好事儿。

因此你可以考虑采用4核8G的机器来部署支付系统,那么你的JVM进程至少可以给4G以上内存,新生代在里面至少可以分配到2G内存空间

这样子就可以做到可能新生代每秒多1MB左右的内存,但是需要将近半小时到1小时才会让新生代触发Minor GC,这就大大降低了GC的频率。

举个例子:机器采用4核8G,然后-Xms和-Xmx设置为3G,给整个堆内存3G内存空间,-Xmn设置为2G,给新生代2G内存空间。

而且假设你的业务量如果更大,你可以考虑不只部署3台机器,可以横向扩展部署5台机器,或者10台机器,这样每台机器处理的请求更少,对JVM的压力更小。


小结

从一个支付系统案例出发,带着大家一点点计算了这个系统在日百万交易的压力下,部署3台机器的场景下,每秒钟每台机器需要处理多少笔订单,每笔订单要耗时多久处理,每秒钟会对JVM占据多大内存空间,根据这个横向扩展预估整个系统每秒需要占据多大内存空间。

接着根据上述数据模型推算出,在不同的机器配置之下,你的新生代大致会有多大的内存空间,然后在不同的新生代大小之下,多久会触发一次Minor GC

为了避免频繁的GC,那么应该选用什么样的机器配置,部署多少台机器,给JVM堆内存多大的内存空间,新生代多大的内存空间。

根据这套配置,就可以推算出来整个系统的运行模型了,每秒钟创建多少对象在新生代,然后1秒之后成为垃圾,大概系统运行多久,新生代会触发一次GC,频率有多高 。


相关文章
|
27天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
211 1
|
8天前
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
42 10
|
17天前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
21天前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
54 7
|
21天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
46 5
|
24天前
|
监控 Java 编译器
Java虚拟机调优指南####
本文深入探讨了Java虚拟机(JVM)调优的精髓,从内存管理、垃圾回收到性能监控等多个维度出发,为开发者提供了一系列实用的调优策略。通过优化配置与参数调整,旨在帮助读者提升Java应用的运行效率和稳定性,确保其在高并发、大数据量场景下依然能够保持高效运作。 ####
27 1
|
26天前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
27天前
|
监控 Java 测试技术
Elasticsearch集群JVM调优垃圾回收器的选择
Elasticsearch集群JVM调优垃圾回收器的选择
48 1
|
1月前
|
监控 Java 编译器
Java虚拟机调优实战指南####
本文深入探讨了Java虚拟机(JVM)的调优策略,旨在帮助开发者和系统管理员通过具体、实用的技巧提升Java应用的性能与稳定性。不同于传统摘要的概括性描述,本文摘要将直接列出五大核心调优要点,为读者提供快速预览: 1. **初始堆内存设置**:合理配置-Xms和-Xmx参数,避免频繁的内存分配与回收。 2. **垃圾收集器选择**:根据应用特性选择合适的GC策略,如G1 GC、ZGC等。 3. **线程优化**:调整线程栈大小及并发线程数,平衡资源利用率与响应速度。 4. **JIT编译器优化**:利用-XX:CompileThreshold等参数优化即时编译性能。 5. **监控与诊断工
|
24天前
|
存储 IDE Java
实战优化公司线上系统JVM:从基础到高级
【11月更文挑战第28天】Java虚拟机(JVM)是Java语言的核心组件,它使得Java程序能够实现“一次编写,到处运行”的跨平台特性。在现代应用程序中,JVM的性能和稳定性直接影响到系统的整体表现。本文将深入探讨JVM的基础知识、基本特点、定义、发展历史、主要概念、调试工具、内存管理、垃圾回收、性能调优等方面,并提供一个实际的问题demo,使用IntelliJ IDEA工具进行调试演示。
26 0