探索JVM的底层秘密(二)——JVM调优实战之预估调优

不知道大家有没有听过扁鹊三兄弟的故事。

故事是这样的:

魏文王问扁鹊:你们三兄弟都精通医术,谁是医术最好的呢?

扁鹊回答:大哥最好,二哥次之,我最差。

魏文王不解的问:为什么这样说呢?

扁鹊答:大哥治病是在病人发作之前,那时候病人自己不觉得有病,但大哥就下药铲除了病根,使他的医术难以被人认可,所以没有名气;

二哥治病是在病起之初,症状尚不十分明显,病人也没有觉得痛苦,二哥就能药到病除,所以大家的印象就是小病找二哥;

我治病是在病人危急时刻,病人痛苦万分,家人心急如焚,他们看到我治病时在经脉上扎针穿刺,或以毒试毒,或动大手续使病人减轻痛苦直至痊愈,所以我闻名天下。

在这里插入图片描述

为什么要讲这个故事呢?

因为JVM调优跟这个故事很像。JVM调优也有这三个阶段

1. 在项目部署到线上之前,基于可能的并发量进行预估调优
2. 在项目运行过程中,部署监控收集性能数据,平时分析日志进行调优
3. 线上出现OOM,进行问题排查与调优

这里每一点展开来讲涉及到的知识点都比较多,打算用三篇文章讲清楚每一阶段的调优,本篇文章讲第一阶段,主要讨论堆区调优。

JVM调优

很多同学可能不太理解:为什么要做JVM调优?

一、防止出现OOM

即在系统部署之前,根据一些关键数据进行预估不同内存区域需要给多少内存合适

二、解决OOM

即线上出现了OOM,应该如何调优以保证程序能正常运行

二、减少full gc出现的频率

这个主要是堆区,如果设置的不合理就会频繁full gc,导致系统运行一阵暂停一阵,导致体验下降

场景

这里以亿级流量秒杀电商系统为例:

  1. 如果每个用户平均访问20个商品详情页,那访客数约等于500w(一亿 / 20)
  2. 如果按转化率10%来算,那日均订单约等于50w(500w * 10%)
  3. 如果40%的订单是在秒杀前两分钟完成的,那么每秒产生1200笔订单(50w * 30% / 120s)
  4. 订单支付又涉及到发起支付流程、物流、优惠券、推荐、积分等环节,导致产生大量对象,这里我们假设整个支付流程生成的对象约等于20K,那每秒在Eden区生成的对象约等于20M(1200笔 * 20K)
  5. 在生产环境中,订单模块还涉及到百万商家查询订单、改价、包邮、发货等其他操作,又会产生大量对象,我们放大10倍,即每秒在Eden区生成的对象约等于200M(其实这里就是在大并发场景下可以考虑服务降级的地方,架构其实就是取舍)

这里的假设数据都是大部分电商系统的通用概率,是有一定代表性的。

如果你作为这个系统的架构师,面对这样的场景,你会如何做JVM调优呢?即将运行该系统的JVM堆区设置成多大呢?

前置知识

那做JVM调优需要什么的基础呢?

  1. 了解Java中基本数据类型的字节长度

public class TestLength {
    public static void main(String[] args) {
        System.out.println("Byte.SIZE=" + Byte.SIZE / 8);
        System.out.println("Character.SIZE=" + Character.SIZE / 8);
        System.out.println("Short.SIZE=" + Short.SIZE / 8);
        System.out.println("Integer.SIZE=" + Integer.SIZE / 8);
        System.out.println("Long.SIZE=" + Long.SIZE / 8);
        System.out.println("Float.SIZE=" + Float.SIZE / 8);
        System.out.println("Double.SIZE=" + Double.SIZE / 8);
    }
}
  1. 深刻理解如何计算对象大小及指针压缩(之后文章我将为大家详细解释)

  2. 深刻理解JVM内存模型

  3. 动态对象年龄判断

JVM并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

垃圾判断算法

gc过程中需要知道哪些对象能回收,哪些对象不能回收,那如何判断呢?就是靠这个算法。这里我就不展开讲了,学过JVM实战的同学我相信对这两种算法都很了解了。

1、引用计数法
2、可达性分析算法(GC Root)

如何调优

这里我们以内存32G服务器来计算。

JVM的堆区最大值默认是物理内存的四分之一,即8G。新生代、老年代的比例默认是1:2,即新生代约等于2.7G,老年代约等于5.4G。新生代中Eden区、From区、To区的比例是8:1:1,即Eden区约等于2.2G,From区、To区各占约270M。

前面我们计算了亿级流量并发系统在高峰期时Eden区每秒产生大约200M的对象,如果在部署系统时未对JVM做任何调优,那么系统运行11s左右(2200M),Eden区就会被充满,就会产生young gc。一般来说整个付款环节3s完成算快的,我们这里就按3s来计算,那么在产生young gc时,Eden区有约600M的对象无法回收,因为600M已超过To区的大小,会触发空间担保机制,这600M的对象会直接被移入老年代。按照这个节奏,程序运行一分半种多点就会产生full gc,引起STW,这在付款环节是不可接受的,所以我们需要做调优。

那如何做调优呢?我们需要反向来推理:为了保证不触发空间担保机制,From区、To区都设置成600M,那个新生代就是6G、老年代就是12G。这样一个JVM就吃掉了18G的内存,所以还需要做其他事情:将这个服务器上其他比较吃内存的服务移走,以保证其他服务的正常运行。这里你为了保险起见,你可以设置得更大,具体调优的数值各位童鞋视实际情况而定吧。

这里有个注意点:看网上有些老师的视频或文章, 他们调优的时候将老年代设置的大于新生代,我觉得这个方案非常危险。 道理同学们可以自己想想。我不建议同学们这样做。

这篇文章就把预估调优堆区这件事情讲清楚了,下篇咱们再将线上产生OOM调优讲清楚。

如果你看到这里,那么恭喜你,对JVM的预估调优GET到了正确的姿势。喜欢本篇文章就请关注+三连支持下博主吧!有问题也可以发到留言区,博主将会给大家解疑。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值