在上线部署一个系统时,应该如何根据系统的业务量来合理的设置 JVM 的堆内存大小?

场景:一个日交易百万的支付系统,部署在三台服务器上,如何去优化 JVM?image.png

  • 首先,考虑每日百万交易的支付系统的压力在哪里?
    • 最核心的环节,就是用户发起支付请求,支付系统需要生成一个支付订单,支付订单需要记录很多的信息,估算 20 个字段;
    • 如果每日百万交易,从 JVM 层面,支付系统最大的压力,就是每天在 JVM 内存里会创建和销毁 100万个支付订单;
  • 支付系统每秒钟需要处理多少个支付订单?
    • 假设支付系统一天共有3小时的高峰期,分布在中午和晚上,而在这几个小时的高峰期内系统处理了 100万个交易;
    • 100万个交易平均到 3个小时,估算每秒处理 100 个订单;
    • 3台机器,每台机器估算大概处理 30 个订单/秒;
  • 每个支付订单处理需要耗时多久?
    • 每台机器上 JVM 每秒在新生代里创建 30个支付订单的对象,然后做一些处理,如填充数据、订单写入数据库,估算订单处理完毕需要耗时 1s;
    • 1s 后,这个30个订单对象在新生代里就变成垃圾对象了;
  • 每个支付订单需要多大的内存空间?
    • 计算对象的内存空间,可以简单的估算每个变量字段的类型所占的字节个数,如 Int 4个字节、Long 8个字节;
    • 一般订单这种核心类大概有20个变量字段,一个对象大小一般大概在几百字节左右;
    • 尽量估大一点,就估算一个支付订单对象占 500字节的空间,不到 1kb;
  • 每秒30个支付订单请求,需要占用多少内存空间?
    • 30 * 500 Byte = 15000 Byte,大概 15kb 左右;
    • 支付系统的支付订单对象,估算每秒就只占用 kb 级别的内容空间;
  • 以点及面,预估完整的支付系统每秒占用多少内存空间?
    • 之前的分析,全部都是基于一个支付系统业务流程中的一个支付订单对象来分析的,其实那只是一小部分而已,真实的支付系统线上运行,肯定每秒会创建大量其他的对象;
    • 依据系统的访问压力和核心对象的内存占据,将这种估算扩展到整个支付系统;
    • 假设,除了支付订单对象,每个订单请求还会创建其他数十种对象,可以将之前计算结果扩大 10倍~20倍,其他对象占据的空间大概 150kb~300kb 左右;
    • 所以,对于整个支付系统进行估算,每秒大概占据内存 几百KB~1M 之间;
    • 这里往大了估算,支付系统每秒占 1M 内存空间;
  • 支付系统的 JVM 堆内存怎么设置?
    • 一般来说这种线上业务系统,常见的机器配置是2核4G,或者是4核8G;
    • 2核4G的机器来部署:
      • 机器有4G内存,但是机器本身也要用一些内存空间,最后你的 JVM进程最多就是2G内存;
      • 这 JVM 进程的 2G 内存,还得分配给方法区、栈内存、堆内存几块区域,那么堆内存可能最多就是个1G多的内存空间;
      • 1G 的堆内存还分为新生代和老年代,老年代给 几百MB 的内存空间,那么留给新生代的内存空间也只有 几百MB 了;
      • 支付系统每秒占用 1M 的内存,那么频繁创建和销毁的只有 几百MB 的新生代,会运行几百秒后新生代空间就满了,导致频繁触发 Minor GC,影响线上系统的性能稳定性;
    • 用4核8G的机器来部署:
      • 机器有8G内存,JVM 进程至少可以给 4G 以上内存,堆内存至少可以给 3G,新生代至少可以给 2G;
      • 这样,支付系统每秒占用 1M 内存,JVM 新生代空间足以运行将近 一小时 才会让新生代触发 Minor GC,大大降低了 GC 的频率;
    • 示例:机器采用 4核8G,然后 -Xms和-Xmx设置为3G,给整个堆内存3G内存空间,-Xmn设置为2G,给新生代2G内存空间;

image.png