java 线上调优_一次惊心动魄的线上JVM性能调优笔记

00ffb3722c0de6995efedfefa100b694.png

转载是一种动力 分享是一种美德

忙了一天,结果发现好像什么也没做。

请原谅我又标题党了,与其说是性能调优不如说是找bug,但对我来说确实是件大事,毕竟是线上出了问题,渠道找运营反应,运营催我修复,于是带着沉重的心情忙活了一天。

都是使用内存缓存惹的祸,都是为了QPS!继续说内存缓存那些事。由于只会有一个定时任务发生写操作,故使用一个静态Map缓存数据。起初是由于忽略了其它服务以及第三方调用接口是多线程,导致数据更新不及时,这可以改为使用volatile声明即可,保证定时任务更新数据对其它线程可见。之所以不用线程安全的Map也是出于性能考虑。

然而,再精心的设计也会有Bug的时候,坑了我一整天的时间。此次并非堆大小没配置,但也确实堆大小导致的bug,虽然程序没有挂掉,但是数据已经不更新了。数据从数据库中加载到内存需要做一些业务上的处理,根据配置的过滤规则需要过滤掉一些屏蔽某些渠道的数据,然后将根据渠道id与其能访问到的数据映射到Map中。根据测试预估数据缓存在内存会占用1.6g内存,我给jvm设置了2.3g左右的堆大小,从逻辑上看并不存在什么问题,但却少考虑了数据的交换问题。

来个伪代码:

{

Var  数据1 = 从数据库加载;

Var  数据2 = 从数据库加载;

Var  数据3 = 从数据库加载;

………

Var  数据10 = 从数据库加载;

Var  最终数据 = 数据1根据数据2~数据10过滤后;

内存缓存的数据 = 最终数据;

}

假设数据1到数据10占内存总和为2.0g,,执行到“内存缓存的数据=最终数据;” 这行代码时,内存总共消费2.0G,执行完这行代码后也就是方法执行完后内存只耗1.6g,因为产升了gc。过滤数据过程中,用于保存临时结果的ArrayList之类的对象不会导致内存飙升太严重,因为数组只是存储对象的引用。

然后当任务执行第二次的时候,由于当前缓存已经消耗了1.6g,在第二次执行“内存缓存的数据=最终数据;”这行代码之前,严格来说是这个方法执行完之前,就需要堆大小大于“当前缓存数据1.6g”+“从数据库中加载的数据1~数据10占的2.0g” = 3.6g 程序才能正常运行。

所以问题清晰了,就是这样导致的内存溢出OutOfMemoryError异常。由于这是定时任务抛出的异常,并未导致服务挂掉,只是执行加载数据的那个任务线程挂掉了。结果就是缓存的数据并未得到更新,永远都是第一次加装的数据,因为“内存缓存的数据=最终数据;”这行代码永远得不到第二次执行。

使用“jstat -gcutil 进程id” 可以看到系统频繁的full gc,full gc次数是young gc的一半,短短几分钟内执行了5次full gc。想起某个视频中学到的“大对象直接进入年老代”,就是大的对象会直接进入年老代,而年老代已经不够容纳新的对象进行,或者已经接近满了,于是就会触发full gc。

谈谈我对“大对象直接进入年老代”的理解:ArrayList以及Map都只是存对象的引用,即便一个ArrayList存了几万个对象,但它所占内存并不是这一万个对象所占内存的总和,这句应该都能理解吧?他的大小只是存储几万个对象的引用的大小。

假如有一个User类,一个User对象占1m大小:

User{

byte[1024*1024]   //注意:是基本数据类型数据,占1024*1024个字节

}

而如果ArrayList 存了一万个User对象,那它占的内存大小就是一万*4个字节,没有异议吧?那这也是个大对象了。是不是大对象是根据你配置的堆大小而言的,如果一个对象存不进年轻代的Eden区,那它就是个大对象。由于ArrayList又是动态分配大小的,每往ArrayList中add一个对象都可能导致动态分配内存,当添加的对象很多时,这个ArrayList就变成了一个大对象,再往里面add对象就会发生两个大对象的内存拷贝,这是一个值得考虑的问题,所以这里又得出了在new ArrayList时指定大小new Array(10000)提升的性能的重要性。

下面给出关于jdk.8 jstat命令的使用以及结果的各列所代表的意思。不知道的学一下,知道的就复习一下,不需要背,有个印象就好了,需要的时候百度即可。

声明:pid ==> 进程id

垃圾回收统计>jstat -gc pid

结果列说明:

S0C:第一个幸存区的大小

S1C:第二个幸存区的大小

S0U:第一个幸存区的使用大小

S1U:第二个幸存区的使用大小

EC:伊甸园区的大小

EU:伊甸园区的使用大小

OC:老年代大小

OU:老年代使用大小

MC:方法区大小

MU:方法区使用大小

CCSC:压缩类空间大小

CCSU:压缩类空间使用大小

YGC:年轻代垃圾回收次数

YGCT:年轻代垃圾回收消耗时间

FGC:老年代垃圾回收次数

FGCT:老年代垃圾回收消耗时间

GCT:垃圾回收消耗总时间

新生代垃圾回收统计>jstat -gcnew pid

结果列说明:

S0C:第一个幸存区大小

S1C:第二个幸存区的大小

S0U:第一个幸存区的使用大小

S1U:第二个幸存区的使用大小

TT:对象在新生代存活的次数

MTT:对象在新生代存活的最大次数

DSS:期望的幸存区大小

EC:伊甸园区的大小

EU:伊甸园区的使用大小

YGC:年轻代垃圾回收次数

YGCT:年轻代垃圾回收消耗时间

老年代垃圾回收统计>jstat -gcold pid

结果列说明:

MC:方法区大小

MU:方法区使用大小

CCSC:压缩类空间大小

CCSU:压缩类空间使用大小

OC:老年代大小

OU:老年代使用大小

YGC:年轻代垃圾回收次数

FGC:老年代垃圾回收次数

FGCT:老年代垃圾回收消耗时间

GCT:垃圾回收消耗总时间

总垃圾回收统计>jstat -gcutil pid

结果列说明:

S0:幸存1区当前使用比例

S1:幸存2区当前使用比例

E:伊甸园区使用比例

O:老年代使用比例

M:元数据区使用比例

CCS:压缩使用比例

YGC:年轻代垃圾回收次数

FGC:老年代垃圾回收次数

FGCT:老年代垃圾回收消耗时间

GCT:垃圾回收消耗总时间

更多jstat的用法请自行百度学习。jstack -l pid是查看线程信息、jmap -heap pid是查看堆信息、jps查看java进程号。如果在服务器上执行jps指令获取不到进程信息,请切换到运行java服务的用户,或者sudo su切换到root用户。

如果觉得对您有帮助,请赏我个“好看”,记得动动手指哦!

f69362dcb319786095832f8f866dd124.png

公众号:java艺术

扫码关注最新动态

79a4bd5841b19fc35e79ad7faea3bece.gif

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值