总体思路
- 让服务端应用以服务的模式执行,以获得相应的性能提升支持
- 显式地设置堆容量分配,避免对内存资源不合理的应用
- 选择合适的GC算法,提高性能
- 保存GC日志,作为优化的依据
- 保存OOM时的内存信息,以提高OOM排查效率
- 开启JMX服务,以方便监测JVM状态
注:对于堆容量分配和GC算法的选择需要有具体测试数据提供依据,否则可能无法提高性能,甚至降低性能。
让服务端应用以服务的模式执行
-server
开启 JVM 的 server 模式,以支持 JIT编译等相关特性
显示地设置堆容量分配
-Xms -Xmx
根据具体的可用资源规划,显式设置堆大小,可以减少 JVM 自动调整堆大小带来的开销
单位:g | m | k
参数内容示例:
-Xms1g
-Xmx4g
-XX:MaxMetaspaceSize
JDK8中,元数据区容量默认可以自动增长。为了稳定起见,可根据实际情况设置一个明确的上限
单位:g | m | k
参数内容示例:-XX:MaxMetaspaceSize=512m
-Xmn
设置新生代容量
单位:g | m | k
参数内容示例:-Xmn512m
选择合适的GC算法
注:GC算法的选择需要根据实际应用情况选择。此处仅针对(老年代)采用CMS GC时的一般情况。
在“吞吐量优先”的应用,也许应考虑 Parallel GC。
而 G1 GC 则是兼顾 吞吐量 和 停顿时间的 GC。
CMS GC 在大堆情况下会因为内存碎片导致严重停顿;所以堆大于16G时可能得考虑用G1,堆大于30G时则采用CMS得非常谨慎。
而且 JDK9 中 CMS 已被标记为废弃。
通常,G1 是非常值得考虑的GC。当然 JDK11 中的 ZGC 也是值得期待的。
《常见垃圾收集器》
-XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled
使用 CMS 作为GC方式,且开启并行标记,以减少服务端延迟时间
-XX:+UseParNewGC
针对新生代的GC;是Serial GC的多线程版本
-XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction
CMS GC算法默认使用启发式的触发规则。这往往会导致老年代快慢的时候才触发GC。
显式地设置这个触发规则可以使得GC行为更能被预测,并减少Full GC带来的性能损耗(如,STW stop-the-world JVM暂停)。
- +UseCMSInitiatingOccupancyOnly 可以取消默认的启发式触发规则;
- CMSInitiatingOccupancyFraction 则可以设置具体什么时候触发CMS GC
- 这个值需要根据具体部署环境下的老年代使用情况进行调优。如果数值较小,可能会导致CMS GC过于频繁;如果数值较大,可能会导致CMS GC触发时机太晚甚至“并发模式失败”
- 一般可以将该值设置为70,即老年代使用量达到70%时会触发CMS GC
参数内容示例:-XX:CMSInitiatingOccupancyFraction=70
-XX:+ScavengeBeforeFullGC
在执行 Full GC 前执行一次 Minor GC可以较少老年代中对象“意外”存活的现象。
这些老年代对象被新生代中的对象所引用, 但这些新生代对象其实已经可以被回收了,所以这些老年代对象其实也应被回收
-XX:+CMSScavengeBeforeRemark
在执行CMS Remark阶段前,执行一次 Minor GC,以降低STW的时间。
通过 Minor GC 可以减少新生代对老年代对象的引用,这样可以减少根对象(GC Roots)数量,从而降低 CMS Remark 的工作量
保存GC日志
-XX:+PrintGCDateStamps -verbose:gc -XX:+PrintGCDetails -Xloggc:log_file_path
将GC日志输出到指定的文件中
参数内容示例:-Xloggc:/opt/log/gc.log
-XX:+PrintAdaptiveSizePolicy
输出 GC 策略的详细信息,包括:
- Minor GC 中存活下来的对象的内存占用量
- Minor GC 中进入老年代的对象的内存占用量
- Minor GC 的起始时间
- ...
GC 的某些行为是根据运行时情况适应性触发的,通过这些详细信息可以让我们更好地理解为什么会出现我们不希望发生的行为。
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles -XX:GCLogFileSize
与上一组设置配合,设置GC日志文件的大小上限与数量
参数内容示例:
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=10m
保存OOM时的内存信息
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath
一般,OOM出现的频率不高,且OOM的复现成本也比较高,为了方便排查,需要在OOM出现时让JVM保存相关内存信息
参数内容示例:-XX:HeapDumpPath=/opt/log/heap.hprof
开启JMX服务
-Djava.rmi.server.hostname
一台计算机有两个IP,一个对外,一个对内,是很常见的网络部署设置。为了避免JVM自行选择IP带来的不确定性,需要显式地设置JVM上的服务所使用的IP。
注:如果应用本身已经有更明确的 IP选项,则应以具体应用为准,而不是使用该选项提供的值。
如,假设某个Java进程需要用IP1对外提供HTTP Web服务,同时需要用IP2对内其它系统提供dubbo服务,则可以显式地配置dubbo服务所使用的IP2。
当然,如果条件允许,拆分该Java进程,尝试利用“微服务”概念也许是个更好的做法。
参数内容示例:-Djava.rmi.server.hostname=10.1.100.123
-Dcom.sun.management.jmxremote.port
设置该端口可方便在JVM运行时通过一些管理工具来检测运行状况(如,JMC)
参数内容示例:-Dcom.sun.management.jmxremote.port=6789
在确保所运行网络环境安全的情况下,也即只有授权用户才能连接到上述端口,可关闭该管理的权限认证,以方便连接:
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false