5.GC调优
1. 调优领域
- 内存
- 锁竞争
- CPU占用
- IO
- GC
2. 确定目标
低延迟/高吞吐量? 选择合适的GC
CMS G1 ZGC
ParallelGC
Zing
3. 最快的GC是不发生GC
首先排除减少因为自身编写的代码而引发的内存问题:
查看Full GC前后的内存占用,考虑以下几个问题:
数据是不是太多?
resultSet = statement.executeQuery(“select * from 大表”);
数据表示是否太臃肿?
对象图
对象大小,Java中new一个Object或者包装类型对象,至少16字节。
是否存在内存泄漏?
static Map map = HashMap(),当我们不断的向静态的map中新增对象且不移除,就可能造成内存吃紧。
可以使用软引用,弱引用来解决上面的问题,因为它们可以在内存吃紧时,被定期回收。也可以使用第三方的缓存中间件来存储上面的map中的数据。
4. 新生代调优
新生代的特点:
当我们new一个对象时,会先在伊甸园中进行分配,所有的new操作分配内存都是非常廉价且速度极快的
TLAB(Thread-Location Allocation Buffer):当我们new一个对象时,会先检查TLAB缓冲区中是否有可用内存,如果有则优先在TLAB中进行对象分配。
死亡对象回收的代价为零
大部分对象用过即死(朝生夕死)
MInor GC 所用时间远小于Full GC
新生代内存越大越好么?
不是:
新生代内存太小:频繁触发Minor GC,会 STW,会使得吞吐量下降。
新生代内存太大:老年代内存占比有所降低,会更频繁地触发Full GC。而且触发Minor GC时,清理新生代所花费的时间会更长。
新生代内存设置为能容纳 并发量*(请求-响应)(对象)
的数据为宜。
5. 幸存区调优
幸存区最大能够保存 当前活跃对象+需要晋升的对象。
晋升阈值配置得当,让真正要长时间存活的对象尽快晋升到老年代。而不要让短时间存活的对象也晋升到老年代,无谓地延长本来要作为垃圾回收对象的存活时间,占用多余的内存空间
6. 老年代调优
以CMS为例:
CMS的老年代内存越大越好。
先尝试不做调优,如果没有 Full GC 那么说明当前系统暂时不需要优化,否则,就先尝试调优新生代。
观察发生Full GC 时老年代的内存占用,将老年代内存预设调大 1/4 ~ 1/3,划分更合理的内存给老年代使用,减少老年代fullGC的产生。
-XX:CMSInitiatingOccupancyFraction=percent
: 老年代的空间占用,占老年代总内存的多少就用CMS进行垃圾回收,值越小,触发CMS回收器垃圾回收的时机就越早,一般设置为70%~75%
7. 案例
当Full GC 和 Minor GC 调用频繁。
通过调整新生代内存大小,解决GC调用频繁
当请求高峰期发生Full GC,单次暂停时间特别长(若选择CMS),业务要求低延迟
通过gc日志,查看CMS哪个阶段好费时间较长,假如响应时间长发生在重新标记阶段,通过
-XX:+CMSScavengeBeforeRemark
: 在重新标记之前,先将新生代的对象做一次垃圾回收,减少新生代对象的数量,进而减少重新标记阶段耗费的时间
在老年代充裕的情况下,发生Full GC (jdk1.7)
jdk1.7的方法区是由元空间实现,jdk1.7以前方法区由永久代实现;jdk1.7及以前,永久代空间不足也会触发整个堆内存的fullGC;
解决方法: 增大永久代的初始值和最大值,保证fullGC不会再发生