零 前述
本文以 G1 作为主视角来做对策分析。
jdk8 中将 GC 调整为 G1:
-XX:+UseG1GC
一 从编码层面来说
1 减少对象的创建频率
尽量复用以前存在的对象,减少对象创建,那么就能减少 gc 的频率。
方法有:
- 单例模式
- 享元模式
- 静态对象
- 枚举类
- 对象池技术
- 用 StringBuilder 和 IntegerCache 等方法去代替直接创建对象
2 从根本上减少堆内存的使用
使用其它内存方式代替堆内存。当一个对象的使用生命周期被囊括在栈的生命周期内,此时 jvm 会通过逃逸分析判断该对象不需要在堆上分配内存,只需要创建在栈内存中就可以了。
- 栈内存是没有 gc 的,等栈的生命周期结束就被全部删除了
- 注意设置好 jvm 栈内存的大小,不要 OOM
- 也可以使用 Unsafe 去操作元空间的内存,但是要注意控制手动释放
涉及参数设置:
设置栈内存的大小,单位 kb:
-Xss:1024
开启逃逸分析,默认就是开启的:
-XX:+DoEscapeAnalysis
3 小对象代替大对象
小对象对于 jvm 来说,是会直接创建在 eden 区的,但是大对象则会直接被分配在老年代。老年代比 eden 大很多,gc 起来更加费力。
- 默认情况下,只要 eden 区有足够的连续空间,不论多大的对象都会分配在 eden 区,这会导致 eden 区可能在短期内存在内存“碎片”,所以可以设置好 jvm 对大对象定义的阈值
- 尽量拆分大对象
- 如果确实有大对象,那么就尽量使用 part 1 中说到的方法,让它存在的时间长一些
- 如果确实有大对象且朝生夕死,那么尽量使用 part 2 中说到的方法,且注意设置好 jvm 栈对大对象的定义
涉及参数设置:
超过这个数 kb 大小的对象会被直接分配在老年代,默认值为无限
-XX:PretenureSizeThreshold=1000
4 合理寿命
如果 eden / s1 / s2 区域的 GC 很频繁,就会使得对象的 GC 年龄过快增长,当年龄增长到一个数值的时候,就可以晋升到老年代中。老年代中的 GC 成本会比其它区域高的多。
涉及参数设置:
设置进入老年代的对象寿命,默认是 15:
-XX:MaxTenuringThreshold=15
对象寿命存放在对象头中,总共占 4 位,所以只能表达 0 - 15 这 16 个数,不在此范围内就会报错。
二 从 jvm 层面来说
1 合理分配堆内存区的大小
增加 eden 区的大小,减少 full gc 的次数。
涉及参数设置:
设置新生代的大小,单位 kb:
-Xmn:1024
设置年轻代占堆内存的最小比例,单位 %:
-XX:G1NewSizePercent=5
设置年轻代占堆内存的最大比例,单位 %:
-XX:G1MaxNewSizePercent=60
2 设置最大停顿时间
注意也别设置的太小,不然会因为回收时间不足而 OOM。
涉及参数设置:
设置最大 stw 时间,单位 ms:
-XX:MaxGCPauseMillis=200
3 关闭检测
如果 gc 时间占用执行时间过长,也会报出 OOM,可以关闭这个选项。
注意,如果确实存在 gc 速度不够的问题,依然会出现 OOM 的,只是关闭了告警而已。
涉及参数设置:
关闭 gc 时间检测:
-XX:-UseGCOverheadLimit
4 增加并行线程数
stw 时期的工作线程数。
涉及参数设置:
在 64 线程的 x86 CPU 上,如果用户未指定 ParallelGCThreads 的值,则默认的计算方式为: ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43
设置并行线程数:
-XX:ParallelGCThreads=10
5 增加并发线程数
非 stw 时期的工作线程数。
涉及参数设置:
默认: ConcGCThreads = ParallelGCThreads / 4
设置并发线程数:
-XX:ParallelGCThreads=10
三 从操作系统层面来说
1 减少 swap
有时候内存不够,Linux 会把进程的内存信息放到磁盘交换区上。从磁盘读写数据会比内存慢很多,容易造成性能的不稳定。
对于 java 开发来说,很多时候一台服务器上也就仅仅部署了一个项目实例而已,所以可以在 Linux 上禁用交换区。
# 禁用交换区命令
sudo swapoff -a
四 排查
1 打印信息
gc 日志是排查最重要的手段。
涉及参数设置:
打印 gc 日志
-XX:+PrintGC
打印 gc 详细日志
-XX:+PrintGCDetails
打印 gc 的日期时间
-XX:+PrintGCDateStamps
打印应用停留时间
-XX:+PrintGCApplicationStoppedTime
老年代分布
-XX:+PrintTenuringDistribution
2 OOM 日志打印
如果出现 OOM,会需要打印 heap 文件。
涉及参数设置:
打印 heap 文件
-XX:+HeapDumpOnOutOfMemoryError
设置 heap 文件的地址
-XX:HeapDumpPath=/path/heap/dump
============
未完待续,想到补充。