0%

gc-common-tunning

1. 打印虚拟机状态

-XX:+PrintGCDetails 开启打印 GC 回收信息详情
-XX:+PrintFlagsInitial 开启打印配置默认值
-XX:+PrintFlagsFinal 开启打印配置最终值
-XX:+PrintCommandLineFlags 开启打印命令行参数

1
java -XX:+PrintFlagsInitial -version

2. 编译模式

-Xmixed 混合模式, 先编译后执行. 默认模式.
-Xint 解释执行模式
-Xcomp 第一次使用就编译成本地代码

1
2
3
4
5
6
7
8
# 会显示默认编译模式 mixed mode
java -version

# 会显示默认编译模式 interpreted mode
java -Xint -version

# 会显示默认编译模式 compiled mode
java -Xcomp -version

3. 堆内存大小

-Xms0m 等价于 -XX:InitialHeapSize, 初始堆大小, 默认 0
-Xmx 等价于 -XX:MaxHeapSize, 最大堆大小

4. 代码缓存

-XX:InitialCodeCacheSize 初始代码缓存大小
-XX:ReservedCodeCacheSize 等价于 -Xmaxjitcodesize, 最大代码缓存大小

-XX:+PrintCodeCache 开启打印代码缓存空间详情
-XX:+PrintCodeCacheOnCompilation 开启每次编译时打印缓存使用情况
-XX:+UseCodeCacheFlushing 开始缓存空间自动清理

JIT 编译的代码都会放到代码缓存中.
如果代码缓存空间不足, 则 JIT 无法继续编译, JVM 会打印出一条警告消息,并切换到 interpreted-only 模式.
也就是说系统将使用解释执行模式工作, 因此性能会下降.

如果代码缓存不断增长, 例如热部署等原因导致的, 那么提高缓存空间知识延迟其空间占满的发生.
为了避免这种情况, 通过 -XX:+UseCodeCacheFlushing 开启, 当代码缓存被占满时, 让 JVM 放弃一部分已编译的代码.

5. 分代比例

-XX:NewRatio 年轻代与老年代的比例, 默认为 1
-XX:SurvivorRatio Survivor 区与 Eden 区的比例, 默认为 8

-XX:NewRatio=2 表示年轻代与老年代的比例为 1:2, 即年轻代占整个堆的 1/3;
-XX:SurvivorRatio=8 表示 Survivor 区与 Eden 区的比例为 2:8, 即一个 Survivor 区占整个年轻代的 1/10

6. 永久代内存大小

-XX:PermSize=64M 初始永久代内存大小
-XX:MaxPermSize=128M 最大永久代内存大小

永久代中包含了虚拟机中所有可通过反射获取到的数据, 比如 Class 和 Method 对象.
full gc 会对永久代中不需要的类进行的回收. 如果永久代内存用完, 会报 OutOfMemoryError: PermGen space.
JDK 1.8+ 开始, JVM 改用 MetaspaceSize 替代 PermSize.

移除永久代的原因:

  1. 某些应用会使用动态代理等方式, 导致类的数量会不断增加, 而启动时固定最大永久代大小, 很难调优
  2. 对于不需要的类, 可以在不暂停的情况下进行回收
  3. 只有 Hotspot 虚拟机才有该区域, 为了与兼容合并其他类型的虚拟机.

7. 元空间

-XX:MetaspaceSize 初始元空间大小, 默认约为 20.8m
-XX:MaxMetaspaceSize 最大元空间大小, 默认没有限制
-XX:MinMetaspaceFreeRatio 在 gc 之后, 最小的 Metaspace 剩余空间容量比例, 用于减少为分配空间所导致的垃圾收集, 默认 40
-XX:MaxMetaspaceFreeRatio 在 gc 之后, 最大的 Metaspace 剩余空间容量比例, 用于减少为释放空间所导致的垃圾收集, 默认 70

1
2
3
4
5
# 通过该命令, 查看 M 即为 Metaspace 的使用率
jstat -gcutil $pid
# 通过该命令, 查看 MC (Metaspace Capacity) 元空间容量, MU (Metaspace Useage) 元空间使用量
# M = MU / MC
jstat -gc $pid

每次元空间内存不足时, 都会触发 full gc, 所以建议 MetaspaceSizeMaxMetaspaceSize 设置成相同大小.
一般项目设置 256m 元空间大小足够, 具体数值, 可以在项目稳定之后, 通过上述命令查看.

7.1. 追踪类的加载和卸载

-XX:+TraceClassLoading 开启追踪类的加载情况
-XX:TraceClassUnloading 开启追踪类的卸载情况

元空间存放类的相关信息, 如果需要定位 Metaspace 频繁触发 full gc, 或者 Metaspace 内存溢出等问题, 可以通过开启以上 2 个开关定位是哪些类的问题.

8. 自动装箱缓存

-XX:AutoBoxCacheMax 自动装箱缓存大小, 默认 128

第一次使用 Integer 类型数据时, 默认会加载 [-128, 127] 范围内的数字, 缓存起来. 可根据需要调整缓存的数字范围
Long 也使用了自动装箱缓存, 但是范围是不可变的

9. 预分配内存

-XX:+AlwaysPreTouch 开启预分配内存

默认情况下, Java 进程启动的时候, 分配的是虚拟内存, 没有真正分配物理内存给 JVM; 当 JVM 需要访问这些内存时, 才真正分配.
这样会有以下问题:
- 第一次 young gc 之前, Eden 区分配对象的速度较慢
- young gc 时, Young 区对象晋升到 Old 区, 这个时候如果需要操作系统真正分配内存, 会导致 young gc 停顿更久.

开启预分配内存, 则在 JVM 启动时直接分配物理内存, 但是会导致 JVM 启动变慢.

10. 重复字符串

-XX:+UseStringDeduplication 开启删除重复字符串

只保留一个 char[] 来优化堆内存

11. 年轻代最大存活阈值

-XX:MaxTenuringThreshold 年轻代最大存活阈值, 默认 15

年轻代对象存活超过该阈值后会晋升到老年代.
由于年轻代使用复制算法, 如果对象存活时间太久, 会导致 Survivor 区对象多过, 影响复制算法性能已经 young gc 停顿时间.

12. System.gc()

-XX:+DisableExplicitGC 开启禁止程序调用 System.gc() 触发 full gc.
-XX:+ExplicitGCInvokesConcurrent 开启使用并发处理程序调用 System.gc() 触发的 full gc.

默认情况下, System.gc() 会导致一个完全停顿的 full gc.
在开启 ExplicitGCInvokesConcurrent 后, CMS 垃圾收集时一些阶段是并行处理的, 减少 full gc
的停顿时长.