亿级流量调优
指针压缩
-XX:-UseCompressedOops指针压缩技术只有64位机器才有。jdk6以后引入的技术,默认是开启的
关闭指针压缩的情况下
通过HSDB用Memory Viewer查看该对象在内存中的分配地址发现类型指针占8字节,0x3其实是数组的长度,前面用一行来存储类型指针
开启指针压缩的情况下
通过HSDB用Memory Viewer查看该对象在内存中的分配地址发现类型指针占4个字节,开启之后,类型指针和数组长度放在一起了
上面的情况分析如下:
跟C/C++中的一个联合体有关联
union u {
int i; // 4B
void p; // 8B
}
该联合体所占大小由最大字节数决定
比如说类型指针:0xffffffff,数组长度为0x00000003,当开启指针压缩时,由于只使用了联合体中的4B,但是联合体本身还是占8B,于是就出现了4B的浪费情况,数组长度占4B,那么把两者拼在一起,就可以节省8B的开销,把数组长度填充到前4B里面,相当于0x0003ffff。
为什么数组对象在关闭指针压缩的情况下有两段填充?不仅要站在学习者的角度去思考问题,更要以设计者的角度去思考
- 1.在开启指针压缩的情况下:
类型指针0xffffffff 8B
数组长度:0xffff 4B
用一个8B就存储下了 - 2.那么在关闭指针压缩的情况下
类型指针0xffffffff 8B
数组长度: 0x0000ffff
由于数组长度只占4B,根据8字节对齐的规范,对于一行内存地址来说,这里就出现了4字节的空间,那么我们知道,数组长度的下一个数据区域就是实例数据,如果说实例数据中正好有一个int类型,那么ok,正好填补到前面的4个字节里面,那如果不是int类型呢?是char类型的呢?虽然说可以存进去,但是作为设计者如何知道这个数据区域存储的实例数据是char类型还是int类型呢?所以说,存进去的话,会比较麻烦,于是需要在对象头的尾部进行对齐填充
指针压缩的底层原理?这个技术能被开发出来归根结底是来自"所有对象大小都必须能被8整除"这条规则。
8字节对齐的言外之意 8(1 000)
如图所示,现在有三个对象
test1:16B
test2:32B
test3:24B
test1的起始地址: 0B
test2的起始地址:16B
test3的起始地址:48B
他们分别对应的二进制:
test1: 0 000
test2:10 000
test3:110 000
可以看到后三位永远是0,那么JVM在存储的时候,就可以这样存储,向右移3位。>>3
也就是说只存储
test1: 0
test2: 10
test3: 110
然后在使用的时候可以左移3位, << 3,再把它还原回去
根本原因在于"8字节对齐"这条规则
堆内存的32G瓶颈从何而来?
在32位机器中,最大内存可以表示为4G,现在机器普遍已经是64位了,按理说,堆内存可以设置超常的大,为什么还会有32G这个瓶颈之说呢?
原因在于,2的32次方表示的内存为4G,在指针压缩使用时,可以将低位进行左移3位,可表示的最大内存也就是2的35次方=32G.
如何扩容,思路是什么?为什么是8字节对齐?
要突破32G内存瓶颈的话,需要改写8字节对齐这条规则,8字节对齐的话,瓶颈是32G,16字节对齐,瓶颈也会扩大一倍,变成64G.但是这样需要考虑内存的使用率,因为在对齐填充的时候,补充的都是空白地址,也就是说在突破瓶颈的同时,也会带来内存空间的浪费。
现在64位机器,并没有完全使用64位地址来表示,只使用了48位,剩下16位保留。也就是说,64位机器上最大使用的内存是2的48次方,原因在于CPU还没有强大到能处理这么大的内存,受制于CPU的算力
例如:
8字节对齐:17B+7B=24B
16字节对齐:17B +15B= 32B(内存的浪费可能更严重)
另外还有一部分原因是:
32G的内存,那么OOM的Dump文件将会变得非常大,普通机器将没法分析
对于一个正常的GC来说,它的频率应该是
5分钟一次YGC
1天一次FullGC
内存大了,虽然发生FullGC的频率小了,但是单次GC花费的时间更长了
JVM调优
为什么调优?
调优的顺序:避免OOM>FullGC>YGC
- 1.避免OOM,
- 2.尽可能减少FullGC,FullGC会引发STW(Stop The World)
到底调什么? - 1.在项目部署到线上之前,基于可能的并发量进行预估调优
- 2.,在项目运行过程中,部署监控收集性能数据,平时分析日志进行调优
- 3.线上出现OOM,进行问题排查与调优
实战:亿级流量系统实战
- 1.如果每个用户平均访问20个商品详情页,那访客数约定于500w(一亿/20)
- 2.如果按转化率10%来算,那日均订单约等于50w(500w * 10%)
- 3.如果30%的订单是在秒杀前两分钟完成的,那么每秒产生1200笔订单(50w*30%/120s)
- 4.订单支付又涉及到发起支付流程、物流、优惠券、推荐、积分等环节,导致产生大量对象,这里我们假设整个支付流程生成的对象为20K,那么每秒在Eden区生成的对象约等于20M(1200笔 * 20K)
- 5.在生产环境中,订单模块还涉及到百万商家查询订单、改价、包邮、发货等其他操作,又会产生大量对象,我们放大10倍,即每秒在Eden区生成的对象约等于200M(其实这里就是在大并发时刻可以考虑服务降级的地方,架构其实就是取舍)
假设响应一个请求的时间为3s(包括付款、扣减库存)
这里的假设数据都是大部分电商系统的通用概率,是有一定代表性的.如果你作为这个系统的架构师,面对这样的场景,你会如何做JVM调优呢?即将运行该系统的JVM堆区设置成多大呢?
分析:
- 1.每秒产生200M对象,Eden区的大小为2.2G,相当于11s就会触发YGC(11 x 200 = 2200M对象),由于假设一个请求响应时间为3s,所以发生GC的时候,会有600M(200M * 3 = 600M)对象回收不掉。GC回收的是标记不到的对象,而S0、S1只有270M,放不下这600M对象,进而触发老年代空间担保机制,放入老年代。对于老年代而言,大小为5400M->9次YGC->触发一次FGC.9x11=99s,触发一次FC,这样的频率8G内存不适合的
- 2.怎么做调优?加内存?怎么加?
S0、S1的内存区域要大于600M,这样,假设S0/S1都为600M,Eden区为4800M(600 x 8 = 4800),约等于6G,那老年代为12G(6G x 2 = 12G),内存加起来的话,12G+6G=18G,但是内存一般都取2的n次幂,所以取16G即可。
如果不调优,则会出现一直触发FGC,又回收不到可用的内存,进而导致JVM发生崩溃
1.堆到底设置成多大比较合适?堆越大越好吗?
如果堆设置的很小-> GC频率可能很高,GC时间也比较短
设置的很大->GC频率会低,但是GC的时长却增加了
调优不是一蹴而就的,需要逐步调整,并观察JVM的GC情况
2.什么样的系统可以进行JVM调优?
首先要区分出是OLAP(在线分析)系统还是OLTP(在线事务查询)系统,如果是OLAP(在线分析)系统,是没有多大调优空间的,因为一次性要查询大量对象,这个是没有办法做调优的,只能加大内存
3.正常GC单次时长100ms,前端和后端的平衡,不能让前端感到明显卡顿