一、预备知识
- 掌握GC相关的VM参数,会基本的空间调整
- 掌握相关工具
- 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则
二、调优领域
- 内存
- 锁竞争
- cpu占用
- io
三、确定目标
- 【低延迟】:CMS、G1(低延迟、高吞吐)、ZGC(jdk12体验) 垃圾回收器
- 【高吞吐量】:ParallelGC 垃圾回收器
四、FullGC前后的内存占用,考虑下面几个问题
- 数据是不是太多?
- resultSet = statement.sexecuteQuery(“select * from 大表”); 大量数据加载到堆内存,扛不住
- 数据表示是否臃肿?
- 对象图:查询时只查用到的字段
- 对象大小:new Object() 都要占16个字节。Integer 24 int 4,如果内存占用过多,可以考虑从一些对象上进行瘦身,能用基本类型的就不用包装类型
- 是否存在内存泄漏?
- static Map map = ,静态的Map,不断向里面put数据,有可能导致溢出
- 可以加上软、弱引用,
- 缓存可以使用第三方缓存实现,尽量不要使用static Map map
五、新生代调优
- 新生代特点
- 所有的new操作的内存分配非常廉价。
- 死亡对象的回收代价是零
- 大部分对象用过即死
- Minor GC的时间远远低于Full GC
- 新生代越大越好吗?
- 新生代空间大了,老年代空间就小了。很容易触发Full GC,暂停时间要比Minor GC暂停时间要长。
- 新生代空间建议在四分之一以上二分之一以下
- 晋升阈值配置得当,让长时间存活对象尽快晋升
六、老年代调优
以CMS为例
- CMS的老年代内存越大越好
- 先尝试不做调优,如果没有Full GC那么已经很优了,否则先尝试调优新生代
- 观察发生Full GC时老年代内存占用,将老年代内存预设调大1/4~1/3
- -XX:CMSInitiatingOccupancyFraction=percent
七、案例
- Full GC和Minor GC频繁
- 分析:说明空间紧张,如果是新生代空间紧张,当我们业务高峰期来了,大量对象被创建,很快新生代空间塞满了(幸存区空间紧张了),对象的晋升阈值就会降低,本来生存周期很短的对象,会被晋升到老年代中,进一步出发老年代垃圾回收导致Full GC频繁发生。
- 解决:通过检查工具检查堆空间大小,确实发现新生代空间太小了,试着增大新生代内存(增加幸存区空间,增大晋升阈值),这样使一些生命周期较短的对象尽可能留在新生代,而不进入老年代,进而减少了老年代Full GC的发生
- 请求高峰期发生Full GC,单次暂停时间特别长(CMS)
- 分析:到底是哪部分暂停时间特别长,查看GC日志,初始标记和并发标记都是比较快的,耗时的时重新标记(CMS在重新标记的时候会扫描整个的堆内存:需要扫描老年代和新生代内存),由于高峰期,新生代可能存在大量对象,导致重新标记会很慢。
- 解决:在重新标记之前,可以先对新生代做一次垃圾回收,减少新生代对象数量,再次重新标记的时候,速度就快了
- 老年代充裕情况下,发生Full GC(CMS jdk1.7)
- 分析:导致Full GC的原因:1.空间不足,导致并发失败。2.空间碎片比较多。从CG日志看没有并发失败或者碎片过多导致的提示。说明老年代内存充裕。那就应该是jdk版本问题,1.7及以前版本,永久带空间不足,就会导致整个堆的一次Full GC出现,1.8及之后,改成了元空间(操作系统的内存空间),
- 解决:增加永久带的内存空间