JVM原理讲解和调优(三)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: JVM原理讲解和调优

四、JVM内存调优

4.1理论部分

   首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。

4.1.1系统级的调优主要的目的

   对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况

1、老年代空间不足

   调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象

2、Pemanet Generation空间不足

   增大Perm Gen空间,避免太多静态对象

   统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间

   控制好新生代和旧生代的比例

3、System.gc()被显示调用

   垃圾回收不要手动触发(生产环境一般配置 -XX:+DisableExplicitGC),尽量依靠JVM自身的机制

4.1.2 调优手段

调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果

.1)新生代设置过小

   一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC

2)新生代设置过大

   一是新生代设置过大会导致老年代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加

   一般说来新生代占整个堆1/3比较合适

3)Survivor设置过小

   导致对象从eden直接到达旧生代,降低了在新生代的存活时间

4)Survivor设置过大

   导致eden过小,增加了GC频率

   另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

4.1.3 简单的GC策略的设置方式

   由内存管理和垃圾回收可知新生代和旧生代都有多种GC策略和组合搭配,选择这些策略对于我们这些开发人员是个难题,JVM提供两种较为简单的GC策略的设置方式

1)吞吐量优先

   JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置

2)暂停时间优先

   JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置

4.2实战部分

调优的一般步骤:①首先收集gc日志,②分析日志中的关键性能指标,③分析GC原因,调优JVM参数。

衡量GC的两个指标:①吞吐量 ②响应时间。理想情况下是高吞吐量,低响应时间,但现实往往两个参数是相悖的。

高吞吐量适合场景:科学计算,后台处理等弱交互场景。

高响应时间适合场景:对响应时间有要求的场景。

初始参数设置(三种收集器调优可以控制的参数)

PARALLEL_OPTION="-xx:+UseParallelGC -XX:+UseParalleloldGC -XX:MaxGCPauseMills=100 -
XX:GCTimeRatio=99 -XX:YoungGenerationSizeIncrement=30"
CMS_OPTION="-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -
XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -
XX:CMSFullGCsBeforeCompaction=5"
G1_OPTION="-xx:+UseG1GC -Xms128M -Xmx128 -XX:MetaspaceSize=64M -XX:MaxGCPauseMillis=100 -
XX:+UseStringDeduplication -XX:StringDeduplicationAgeThreshold=3"
JAVA_OPTS="$JAVA_OPTS  -XX:+UseG1GC -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=$CATALINA_HOME/logs/ -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps -
XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -Xloggc:$CATALINA_HOME/logs/gc.log 
-XX:+PrintHeapAtGC  -XX:+PrintTenuringDistribution"

4.2.1、Parallel GC调优

指导原则:

  • 除非确定,否则不要设置最大堆内存
  • 优先设置吞吐量目标
  • 如果吞吐量目标达不到,调大最大内存,不能让OS使用Swap,如果仍然达不到,降低目标
  • 如果吞吐量能达到,但GC时间太长,则设置停顿时间的目标

设置 Metaspace 大小 -XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M

添加吞吐量和停顿时间参数 -XX:GCTimeRatio=99 -XX:MaxGCPauseMillis=100

修改动态扩容增量 -XX:YoungGenerationSizeIncrement=30

4.2.2、G1 GC调优

操作网站:Universal JVM GC analyzer - Java Garbage collection log analysis made easy

指导原则:

  • 年轻代大小:避免使用-Xmn、-XX:NewRatio等显式设置Young区大小,会覆盖暂停时间目标
  • 暂停时间目标:暂停时间不要太严苛,其吞吐量目标是90%的应用程序时间和10%的垃圾回收时间,太严苛会直接影响到吞吐量

同样的,我们先不设置任何调优参数,只是设置一些初始参数,然后再来做对比,也是以Tomcat为例(之前Parallel GC相关的参数要去掉),如下:

JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -
XX:HeapDumpPath=$CATALINA_HOME/logs/ -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -
XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log -XX:+PrintHeapAtGC -
XX:+PrintTenuringDistribution"

和之前一样,重启Tomcat完成后,把GC日志下载到本地,然后上传到工具上进行分析,可以看到使用了G1后,停顿时间明显小了很多,但吞吐量变化不大,因为G1是停顿时间优先的收集器:

从触发GC的原因可以看到,Metaspace区域发生了一次GC,并且Young GC(Others)的次数也比较多:

同样的,在页面顶端,该工具也提示了我们可以调整Metaspace区域的大小:

那我们就和之前一样,调大Metaspace区域看看:

JAVA_OPTS="$JAVA_OPTS -XX:MetaspaceSize=64M ...略..."

再次将日志文件上传到可视化工具中进行分析,可以看到吞吐量上去了一些:

而且也没有再发生Full GC了:

但是从上图中可以看到Young GC的次数依然很多,我们可以试着将堆的大小调大一些看看。如下:

JAVA_OPTS="$JAVA_OPTS -Xms128M -Xmx128M ...略..."

再次将日志文件上传到可视化工具中进行分析,可以看到吞吐量和停顿时间却变长了一些:

但是Young GC的次数明显少了很多:

我们都知道G1是停顿时间优先的收集器,所以我们可以设置一个停顿时间目标,让G1自己自动调整去达到这个目标。如下:

JAVA_OPTS="$JAVA_OPTS -XX:MaxGCPauseMillis=100 ...略..."

再次将日志文件上传到可视化工具中进行分析,结果是吞吐量上去了一些,但停顿时间却变长了一些:

G1收集器的调优参数无非也就这几个,更多的是要对日志进行分析以及经验的积累,才能得出高效的调优方式。

4.3JVM常见配置汇总和参考文章

堆设置

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:设置年轻代大小

-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

-XX:MaxPermSize=n:设置持久代大小

收集器设置

-XX:+UseSerialGC:设置串行收集器

-XX:+UseParallelGC:设置并行收集器

-XX:+UseParalledlOldGC:设置并行年老代收集器

-XX:+UseConcMarkSweepGC:设置并发收集器

垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

参考文章(要紧跟官方文档):

JVM层GC调优(下)

JVM层GC调优(下)_51CTO博客_jvm调优

jvm的运行时数据区

The Java® Virtual Machine Specification

Metaspace

《JVM故障诊断指南》之4 —— Java 8:从持久代到metaspace | 并发编程网 – ifeve.com

压缩类空间

Java8 Non-Heap 中的metaspace 和compressed class space解释_chenchene128的博客-CSDN博客

CodeCache

java codeCache_mars_nier的博客-CSDN博客

Java 8 Migration: A Funny Thing Happened on the Way to Java 8

GC调优指南:

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html

如何选择垃圾收集器

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html

G1最佳实践

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations

G1 GC的一些关键技术

Java Hotspot G1 GC的一些关键技术 - 知乎

CMS日志格式

https://blogs.oracle.com/poonam/understanding-cms-gc-logs

G1日志格式

https://blogs.oracle.com/poonam/understanding-g1-gc-logs

GC日志分析工具

http://gceasy.io/  

GCViewer

GitHub - chewiebug/GCViewer: Fork of tagtraum industries' GCViewer. Tagtraum stopped development in 2008, I aim to improve support for Sun's / Oracle's java 1.6+ garbage collector logs (including G1 collector)

ZGC:

JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)

相关实践学习
日志服务之数据清洗与入湖
本教程介绍如何使用日志服务接入NGINX模拟数据,通过数据加工对数据进行清洗并归档至OSS中进行存储。
相关文章
|
4天前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【6月更文挑战第30天】**探索JVM性能调优:**关注堆内存配置(Xms, Xmx, XX:NewRatio, XX:SurvivorRatio),选择适合的垃圾收集器(如Parallel, CMS, G1),利用jstat, jmap等工具诊断,解决Full GC问题,实战中结合MAT分析内存泄露。调优是平衡内存占用、延迟和吞吐量的艺术,借助VisualVM等工具提升系统在高负载下的稳定性与效率。
18 1
|
6天前
|
监控 Java 测试技术
Java中的JVM调优技巧
Java中的JVM调优技巧
|
17小时前
|
监控 算法 Java
JVM调优---堆溢出,栈溢出的出现场景以及解决方案
【7月更文挑战第3天】堆溢出(Heap Overflow)和栈溢出(Stack Overflow)是两种常见的内存溢出问题,通常发生在内存管理不当或设计不合理的情况下
9 3
|
4天前
|
监控 负载均衡 Java
Java虚拟机调优技巧及性能监控
Java虚拟机调优技巧及性能监控
|
1天前
|
存储 监控 安全
深入理解Java虚拟机(JVM)原理
深入理解Java虚拟机(JVM)原理
|
6天前
|
Java 编译器
Java健壮性 Java可移植性 JDK, JRE, JVM三者关系 Java的加载与执行原理 javac编译与JAVA_HOME环境变量介绍 Java中的注释与缩进 main方法的args参数
Java健壮性 Java可移植性 JDK, JRE, JVM三者关系 Java的加载与执行原理 javac编译与JAVA_HOME环境变量介绍 Java中的注释与缩进 main方法的args参数
10 1
|
2天前
|
监控 负载均衡 Java
Java虚拟机调优技巧及性能监控
Java虚拟机调优技巧及性能监控
|
3天前
|
监控 Java 调度
探索JVM性能调优,调优不仅是技术挑战,更是成长过程。
【7月更文挑战第1天】探索JVM性能调优:** 本文深入JVM内存模型,关注堆内存与方法区、栈的优化,通过调整-Xms, -Xmx及垃圾收集器参数减少GC频率。探讨了Serial到G1等垃圾收集器的选择策略,利用jstat、jmap等工具诊断性能瓶颈。实战案例中,通过问题定位、内存分析解决Full GC问题,强调开发者需理解JVM原理,运用工具在复杂场景下实现高效调优。调优不仅是技术挑战,更是成长过程。
10 0
|
6天前
|
存储 监控 算法
深入理解Java虚拟机(JVM)原理与调优技巧
深入理解Java虚拟机(JVM)原理与调优技巧
|
7天前
|
存储 缓存 监控
JVM中G1垃圾收集器:原理、过程和参数配置深入解析
JVM中G1垃圾收集器:原理、过程和参数配置深入解析