Skip to content

Latest commit

 

History

History
139 lines (94 loc) · 5.33 KB

04-GC调优.md

File metadata and controls

139 lines (94 loc) · 5.33 KB

GC调优

常见调优参数

  • -Xms 初始堆大小
  • -Xmx 最大堆大小
  • -XX:NewSize 年轻代大小
  • -XX:MaxNewSize 年轻代最大值
  • -XX:PermSize 永生代初始值
  • -XX:MaxPermSize 永生代最大值
  • -XX:NewRatio 新生代与老年代的比例

概述

预备知识

  • 掌握 GC 相关的 VM 参数,会基本的空间调整 掌握相关工具。
  • 掌握相关工具
  • 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则。

查看虚拟机参数命令

java -XX:+PrintFlagsFinal -version | findstr "GC" # windows 下

调优领域

  • 内存
  • 锁竞争
  • CPU 占用
  • IO
  • GC

确定目标

科学计算:追求高吞吐量,响应时间长点没事。

互联网项目:响应时间很重要。

低延迟/高吞吐量? 选择合适的 GC

  • CMS、G1、ZGC(响应时间优先,CMS 用的比较多。)
  • ParallelGC
  • Zing 虚拟机,0 停顿。几乎没有 STW 时间。

最快的GC是不发生GC

首先排除减少因为自身编写的代码而引发的内存问题

查看 Full GC 前后的内存占用,考虑以下几个问题

  • 数据是不是太多?如:resultSet = statement.executeQuery("select * from 大表 limit n")
  • 数据表示是否太臃肿
    • 对象图,数据不需要全部都用,用到哪个查哪个
    • 对象大小
      • new Object() 占16个字节
      • 包装对象 如 Integer 的一个对象头就 16 个字节。Integer 好像占 24 字节
      • 能有基本类型就用基本类型
  • 是否存在内存泄漏
    • static Map map = 不断放对象,不溢出。
    • 建议用软、弱引用
    • 缓存数据不太推荐用 Java 的,用三方缓存实现,如 Redis

新生代调优

内存调优建议从新生代开始。

新生代的特点

  • 所有的 new 操作分配内存都是非常廉价的
    • TLAB thread-local allocation buffer
    • new 的时候先检查 TLAB 中是否有可用内存,有就优先在这块内存区域进行对象的分配。对象的内存分配也有线程安全的问题。TLAB 让每个线程用自己私有的那块伊甸园区进行对象的分配。
  • 死亡对象回收零代价
  • 大部分对象用过即死(朝生夕死)
  • MInor GC 所用时间远小于 Full GC

新生代内存越大越好么?

  • 不是
  • 新生代内存太小:频繁触发 Minor GC,会 STW,会使得吞吐量下降
  • 新生代内存太大:老年代内存占比有所降低,会更频繁地触发 Full GC。而且触发 Minor GC 时,清理新生代所花费的时间会更长
  • Oracle 官方推荐,25%Heap < young generation < 50%Heap
  • 新生代内存设置为可容纳 [并发量 * (请求-响应)] 的数据为宜

幸存区

  • 幸存区需要能够保存当前活跃对象+需要晋升的对象
  • 晋升阈值配置得当,让长时间存活的对象尽快晋升

-XX:MaxTenuringThreshold=threshold 晋升阈值

-XX:+PrintTenuringDistribution 打印信息

Desired survivor size 48286924 bytes, new threshold 10 (max 10) 
- age 1: 28992024 bytes, 28992024 total 
- age 2: 1366864 bytes, 30358888 total 
- age 3: 1425912 bytes, 31784800 total ...

老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经...,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
    • -XX:CMSInitiatingOccupancyFraction=percent 一般设置为 75%~80%

案例

案例 1:Full GC 和 Minor GC 频繁

大量频繁的 GC 说明空间紧张,究竟是那部分空间紧张。如果是新生代的空间紧张,大量的对象被创建,很快就把新生代塞满了。幸存区空间紧张,对象的晋升值减低,很多本来生存周期很短的对象也晋升到老年代去,这样就进一步恶化了。进而触发老年代的 Full GC。

  • 先试着增大新生代内存,垃圾回收就变得不那么频繁了。
  • 增加幸存区的空间和晋升的阈值,让很多生命周期较短的对象留在新生代,进一步让老年代的 Full GC 不那么频繁。

案例 2:请求高峰期发生 Full GC,单次暂停时间特别长(CMS)【业务需求是低延时】

到底是那一部分耗费的时间长?

垃圾回收使用的 CMS。

查看 CMS 的日志,看 CMS 的哪个阶段耗时长。发现重新标记阶段耗时长!

重新标记的时候会扫描整个堆内存(new + old),如果业务高峰,新生代对象比较多,那么扫描时间就很长。在重新标记前先对新生代对象做一次 GC,减少新生代对象的遍历数量。-XX:+CMSScavengeBeforeRemark

回顾 CMS 的几个阶段

  • 初始标记:仅仅标记 GC Roots的对象,并且 STW
  • 并发标记:使用 GC Roots Tracing 算法,进行跟踪标记,不 STW
  • 重新标记:因为之前并发标记,其他用户线程不暂停,可能产生了新垃圾,所以重新标记,STW。
    • 重新标记的过程值追踪在并发标记过程中产生的变动的对象。

案例 3:老年代充裕的情况下,发生 Full GC (CMS jdk1.7)

GC 日志里没有竞争失败,并发失败,碎片过多的错误提示。说明老年代的空间是充裕的。后来发现应用部署在 jdk1.7 里。在 1.7 里,永久代空间的不足也会导致 Full GC,1.8 以后改成了元空间(用的本地内存)不会导致 Full GC。