- 如何充分利用高性能服务器(比如:内存16G)
- 一般的思路是调大堆内存(12G) 但是同时会引起几个问题
- 年轻代GC和老年代GC的时间就会很长,严重的10多s,这样会导致系统不定时的卡顿(GC的stop the world),用户不能操作。
- 当发生堆溢出时,无法dump堆 一般快照文件达到十几G,即使dump下来,也很难分析
- 解决:
- 建立逻辑集群
- 在一台机器上,开启多个服务器,修改端口号,每个服务器分配2G内存,然后前端使用nginx均衡分发
- 不建立集群 将full GC 调至最低,最好可以一天执行一次,然后在午夜手动触发。
- 建立逻辑集群
- 一般的思路是调大堆内存(12G) 但是同时会引起几个问题
- 线上老年代一直在慢速增长(真实线上问题)
- 起源:内存堆使用率超过80%后报警
- 处理:
- 先通过jinfo 查看当前虚拟机配置 发现是由虚拟机自动动态调节老年代新生代大小比例。GC策略使用的是Paraller Scavenge 以系统吞吐量优先的多线程垃圾回收器。(刚开始觉得这种配置不合理,一般在面向用户时使用并发CMS收集器,平缓GC,后来发现由于是专门用来跑work的服务器,这种配置也是合适的)
- 通过jstat -gcutil PID 1000 每1秒打印堆使用情况日志 发现在一次young GC后老年代增长0.16% 年轻代的大部分空间也被回收 于是就想要知道到哪些对象晋升到老年代
- 通过jmap dump一次GC前后的两份快照
- 通过jvisual VM 分析大对象 发现晋升的对象只有两个 都是netty对象。从快照看一切正常。 (一般问题到这就能看出来)
- 后来发现是由于虚拟机动态调节策略,而且是以吞吐量为优先(以空间换时间,以减小新生代大小从而降低新生代回收时间,保证吞吐率)的垃圾回收策略,导致新生代大小特别小(在报警时存活区大小只有2M),部分对象就会直接进入老年代。
- 将动态调节修改为指定新生代大小 将垃圾回收策略改为ParNew策略 程序回复正常。
3. 线上问题2
起源: jvm报警 部分服务器宕机 看日志oom
分析:
- 首先看监控系统看到内存的使用率达到99.97% 堆内存设置1024M(偏小)
- 使用jmap -dump 下载快照 使用jvisualvm 查看到List中 MessageProcLog 对象有70多万个
- 排查代码发现 是一个消息补发的job 查询mongo库 那为啥会有这么多对象呢
- 经过分析发现有重复的数据,而且每5分钟翻一倍
- 看到代码中是在消息推送后 先修改原始消息状态 后 插入新消息的逻辑
- 怀疑是修改未成功,但是插入成功了
- 但是修改不成功会抛异常,代码是顺序执行 理论上插入也不会成功 不合道理啊
- 后来发现job的配置是轮询策略 每个job执行时,查mongo,然后将数据交给线程池异步执行
- 这样的话就会导致 多台机器同时再跑job 而且有可能操作同一条数据,导致数据重复插入
- 我们想了很多策略,比如job单机串行执行,但是怕单机时 不同次的job还是执行同一个数据 导致数据还会重复插入(虽然概率比之前小了很多,但是还可能发生)
- 本着追求彻底完美的精神,最终把逻辑修改为 消息推送后只修改原始消息状态,这样无论单台还是多台机器执行都不会有问题了(无非是同一种状态修改了多次)