【探讨】JVM调优之我的理解一
1 前言
这节我们来看看 JVM 调优,关于 JVM 的一些基础配置,基础内容,大家可以参考我的:【JVM】JVM调优工具命令详解、【JVM】JVM的配置参数汇总分类、【工具使用】【Arthas】平时经常使用到的命令、【JVM】JVM各个内存模型存储内容详解。
我们本节主要是想看下,大家都说 JVM 调优,那调什么呢?调的落点又是什么呢?调的最终目标是要达到什么效果呢?那怎么调呢怎么看当前的JVM的增长变化(jstat)呢?是不是有这些困惑,这节我就来说说我自己的理解哈,有理解不对的地方还请大佬们指定一下。我们常用的还是 JDK1.8 + G1垃圾回收哈,我这里也是拿这个示例。
2 问答
2.1 调优调的是什么、落点是什么
我们的代码最终都是要依托于 JVM这条进程得到执行,在这条进程上比如我们的 main 线程,以及各种各样的线程池等都是要依托于这条 JVM 进程的,我们调优就是要调这个 JVM 的一些运行形态。
那么它的运行形态,我们能干预的是什么?JVM它大概分四块内容:类加载引擎、运行时数据区、执行引擎、本地接口层,大概四块吧,我们所能干预的基本上就是运行时数据区了,当然了还有一些 agent 不管是启动时注入还是运行时远程加载的方式也能干预,但他们应该不算调优,我感觉是更像一种监控或者增强。所以我们能干预的是不是就是运行时数据区,运行时数据区有哪些:从线程角度去看,它分线程私有:栈(虚拟机栈)、程序计数器、本地方法栈,线程共享:方法区、堆,本地方法栈、程序计数器、方法区(类元信息、方法信息)一般我们不需要太去管。
本地方法栈、程序计数器、方法区:这三个为什么说不太管,本地方法栈基本都是 native 都是 JVM跟操作系统之间的交互,我们能管人家什么是不是,计数器也管不了呀,计数器控制线程的执行能管人家啥,方法区或者8以后叫原空间,可以设置原空间的大小,这里边存放的都是一些方法信息、类元信息,就跟我们的系统里的基础数据类似,基本不变的只要够存即可,所以这仨我们都不需要太管。
所以能干预的基本就是:栈、堆了。
栈的话常见的就是 -Xss 控制每个线程分配的栈大小,默认好像是 1M。
java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
这玩意能控制什么?
Java 虽然是面向对象的,但是线程执行起来我的理解其实就是面向过程的,就是调用某个对象的某个方法,而每调用一个方法都会压一个栈帧(包含局部变量表、方法出口、操作数栈)等这些都是需要空间的,所以这个参数也间接表示我们能最深度调用多少个方法。
比如一个方法 1K的话,按默认线程栈 1M 大小来的话,大概最深能调用到1024个方法哈,别理解错是最深能调用1024个方法,不是调用1024个方法,因为中间有的方法调用完会随着压栈弹栈就释放掉了。
堆能调整的就比较多了,常见的:
(1)-Xms:设置JVM启动时的初始堆内存大小。例如,-Xms512m 表示初始堆大小为512MB。
(2)-Xmx:设置JVM允许的最大堆内存大小。这是JVM在运行过程中可以使用的最大堆空间。例如,-Xmx2g 表示最大堆大小为2GB。通常建议将 -Xms 和 -Xmx 设置为相同的值,以避免运行时堆内存动态扩展导致的停顿。
(3)-Xmn 或 -XX:NewSize 和 -XX:MaxNewSize:这些参数用来控制新生代(Young Generation)的大小。新生代是堆的一部分,主要用于存放新创建的对象。-Xmn 用于设置新生代的总大小,而 -XX:NewSize 和 -XX:MaxNewSize 分别设置新生代的初始和最大大小。例如,-Xmn256m 可以设置新生代的大小为256MB。
(4)-XX:SurvivorRatio:这个参数定义了Eden区与一个Survivor区的比例。默认情况下,这个比例通常是8:1。例如,如果新生代大小为100MB,并且 SurvivorRatio 设置为8,则Eden区大小为80MB,每个Survivor区大小为10MB。
(5)-XX:PermSize 和 -XX:MaxPermSize (仅限于Java 7及之前版本):这两个参数用来设置永久代(Permanent Generation)的初始大小和最大大小。永久代主要存放类的元数据信息。从Java 8开始,永久代被元空间(Metaspace)取代,对应的参数变更为 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize。
(6)-XX:MaxTenuringThreshold:这个参数设置对象晋升到老年代之前的存活年龄阈值。默认值因JVM版本不同而有所差异,但一般在0到15之间。较大的值意味着对象需要经历更多的Minor GC才能晋升到老年代。
(7)-XX:+UseG1GC, -XX:+UseParallelGC, -XX:+UseConcMarkSweepGC 等:这些选项用于选择不同的垃圾收集器。G1 GC、Parallel GC 和 CMS GC 是几种常用的垃圾收集算法,每种都有其特定的应用场景和优缺点。
(8)-XX:InitiatingHeapOccupancyPercent(默认45%):这个参数设置触发并发垃圾收集周期的堆占用百分比。适用于G1 GC等并发垃圾收集器。
(9)-XX:MaxHeapFreeRatio(默认70) 和 -XX:MinHeapFreeRatio(默认40):这两个参数用于调整当空闲空间达到一定比例时,堆如何进行收缩或扩张。
另外关于一些排查错误、监控相关的配置:
-XX:+HeapDumpOnOutOfMemoryError 指定堆内存溢出时自动进行Dump
-XX:HeapDumpPath=文件路径:设置在发生内存不足错误(如OutOfMemoryError)时,堆转储文件的存储位置。例如,-XX:HeapDumpPath=/dumps/heapdump.hprof。
-XX:+PrintGCDetails -XX:+PrintGCDateStamps 打印GC日志 添加时间戳或日期戳到垃圾回收日志中,有助于分析特定时间点的性能问题
-Xloggc:gc.log 指定GC日志文件
-XX:+PrintFlagsFinal:打印所有JVM标志及其最终值,这对于查看实际生效的配置非常有用
-XX:+PrintCommandLineFlags:打印出命令行上指定的所有JVM标志,这有助于确认哪些标志被显式设置
-XX:+UseGCLogFileRotation 和 -XX:NumberOfGCLogFiles=n 和 -XX:GCLogFileSize=size:这些选项允许垃圾回收日志进行轮转,避免单个文件过大。你可以设置日志文件的数量和每个文件的最大大小。
所以我们理解 JVM 调优的落点其实就是调 JVM 的配置参数。
2.2 调优目标是什么
调优,那我们要达到一个什么样的效果,才算是好的。什么样才算是坏的呢?坏的可能大家体会过,就是老发生 FullGC感觉系统特别卡甚至直接重启了。
那什么是好的呢?咱整不了那些高端词汇,什么提高吞吐、降低延迟啥的.....
我理解的是对象能及时的在年轻代就被回收掉,很少有对象能晋升到老年代,也就是你可以YoungGC(频率不要太高,5秒就 YoungGC一下那太频繁了),但尽量别发生 FullGC。
2.3 怎么看增长变化、怎么调以及我的困惑
那如何看它的增长变化呢?我咋知道年轻代大概多长时间回收一次?这个其实能看出来,就是 jstat 命令,这个命令还是比较重要的,可以看你的GC以及区域空间的增长变化。
# 比如你有一个Java应用的进程ID为12345,并希望每秒查看一次GC统计数据,共查看10次 jstat -gc 12345 1000 10
然后看下我们的 JVM 的情况:
这是我们生产环境的 POS,我们用的 G1 回收器,你们说我这个 JVM 算正常不。我是觉得还行,不需要太干预它。大佬们还请指点一下。
当我加上 -XX:MaxGCPauseMillis=50 也就是最大停顿时间 50毫秒的时候,不设置年轻代大小的话,G1 为了保证向这个 50毫秒靠拢,它把年轻代压缩的很小,就给了 256M,YoungGC 就会比较频繁,慢慢老年代就会主逐渐增多。
这个我感觉不太好,年轻代空间太少了,然后我把年轻代设置上 -Xmn3G:
我感觉是不是不需要太调。