JVM参数全解析:从内存模型到GC调优的生产配置指南
2026/9/23 8:13:26 网站建设 项目流程

1. JVM参数能不能全交给默认值?

先说结论:默认值本身是用来“能跑”的,不是用来“跑得好”的。我见过太多线上事故,起因就是开发觉得“JVM参数不用配,默认就行”。这话在本地写个hello world没毛病,一旦上了生产环境,默认值就成了定时炸弹。

为什么这么说?JVM的很多默认参数是动态计算出来的,它根据你机器的物理内存、CPU核数、JDK版本甚至是操作系统类型来推断。同一个jar包,部署在8G内存的笔记本上是一个表现,部署在32G内存的服务器上完全是另一个表现。最典型的就是-Xmx,如果没显式配置,JVM默认取物理内存的1/4。你服务器上跑着三四个Java进程,每个都自动吃掉物理内存的25%,几个进程叠加,机器直接就OOM了——这压根不需要什么高并发,进程多了就够你喝一壶。

还有个更隐蔽的问题:默认值会随JDK版本悄悄变化。比如JDK 8默认垃圾回收器是ParallelGC,JDK 9开始换成G1,JDK 14进一步增强了G1。你什么都没改,升个JDK版本,垃圾回收行为完全变了,性能曲线全乱。如果你在生产环境里做了显式配置,至少升级JDK后行为是可预期的,不至于“玄学变慢”。

换句话说,显式配置JVM参数的核心目的就三个:可控、可预期、可排查。可控是说资源的分配你说了算;可预期是说不管部署到哪台机器,行为一致;可排查是说出了问题有log、有dump、有现场。这三条都依赖你主动去配置关键参数,而不是把命运交给JVM的启发式算法。

所以这篇文章,我就把那些“应该显式配置”的JVM参数好好捋一遍。不堆参数,重点讲清楚每个参数的原理、适用场景、配置建议和踩坑记录。顺序上会覆盖内存模型、垃圾回收、诊断排障、部署运维这几个维度,最后给一个可以直接抄的生产配置模板。

2. 内存模型参数:这些不配,出事是迟早的

关于JVM内存,网上经常把“JVM内存模型”和“Java内存模型(JMM)”混着说,这是两回事。JMM是规范,讨论的是多线程可见性那一套;而我们调优时说的内存参数,指的是运行时数据区——堆、栈、元空间、直接内存这块。

2.1 堆内存的“下限”和“上限”:-Xms 与 -Xmx

这是JVM参数里最基础的,没有之一。-Xms是堆内存初始值,-Xmx是堆内存最大值。为什么必须显式配置?

先说-Xmx不配的后果。前面提过,JVM取物理内存的1/4作为默认最大堆,这个在容器环境里尤其致命。Docker容器限制的是容器内存,但JVM默认看的是宿主机的物理内存——你可能开了个-Xmx以为单位是容器内存,实际JVM拿宿主机内存算的,直接超配额被杀掉。虽然JDK 8u191+有了UseContainerSupport会好一点,但只显式配-Xmx依旧比裸奔安全得多。

再说-Xms。JVM启动时不会立即占满最大堆,而是一边用一边扩容。扩容和缩容这个过程是有开销的,涉及堆内存重新分配和GC暂停。高并发应用如果Xms设得很小、Xmx设得很大,业务流量一上来,堆疯狂扩容,响应时间会明显抖动。我的建议是**-Xms-Xmx设置成相同值**,一启动就把堆分配到位,省掉扩容开销。

配置上有两点要注意:

  • 两个值相等会让JVM启动稍慢一些,因为初始化就要分配大块内存,但这个代价在长时间运行的服务面前微不足道。
  • 堆内存大小不要拍脑袋定。经验上留出整个进程内存的50%到60%给堆,剩下给元空间、线程栈、直接内存和JIT编译器。你总内存8G,堆建议给4G到5G左右,而不是随手填个6G、7G。

2.2 新生代参数:-Xmn 与 -XX:NewRatio

新生代大小直接决定了对象分配和Minor GC的频率。它是个“过小频繁GC、过大浪费空间”的典型参数。

-Xmn是直接指定新生代大小,比较简单粗暴。-XX:NewRatio是设置老年代和新生代的比值,默认是2,意思是老年代:新生代 = 2:1,新生代占堆的1/3。

重点说下选多大合适。如果服务对象大多朝生夕死(比如请求处理产生的大量临时对象),新生代可以稍大些,占到堆的1/3到1/2。如果服务里长期存活的大对象多(缓存、连接池),新生代可以小一些。优化目标是让Minor GC后晋升到老年代的对象尽量少,而不是把新生代无限调大——新生代越大,Minor GC单次停顿时间越长,反过来拖累性能。

我个人在G1垃圾回收器下不太建议显式设置-XmnNewRatio。G1和ParallelGC不一样,它自己会动态调整年轻代大小来适配目标停顿时间,你硬设了反而绑住它的手脚。但如果你用的是ParallelGC或者CMS,这两个参数还是值得一配的。这里顺带提一句,网上很多文章把G1和-Xmn混着讲,实操时是要分开看的。

能接受吗?能。但先确认你的垃圾回收器型号,再决定要不要设置新生代比例。不同垃圾回收器对新生代参数的处理差异很大,这是踩坑第一步。

2.3 线程栈大小:-Xss

-Xss指定每个线程的栈大小,默认值因平台而异,Linux x64下通常是1MB。很多人不关心这个,直到业务用了递归、或者在栈里塞了超大本地变量,然后莫名其妙StackOverflowError。

这个参数的核心权衡是:栈越大,单线程能支持的调用深度越大;但线程栈是从进程内存里划出去的,线程一多,栈太大会导致总内存暴涨。比如一个进程开500个线程,每个线程栈1MB,光栈就占500MB。如果缩减到512KB,就是250MB,省一半。

经验上看:

  • 普通业务服务,无深递归、无复杂调用链,-Xss512k绰绰有余。
  • 需要递归或者方法调用层次深的场景,给到1MB甚至2MB。
  • 不要无脑调大,尤其你开了很多线程池线程的时候。

这里有个排查技巧:如果线上高频出现StackOverflowError,先看是不是递归逻辑写bug了,别急着调栈大小。我见过有人为了“解决”栈溢出把-Xss调到8MB,结果溢出依旧——根本就是递归没写退出条件。栈溢出先查代码,再动参数。

2.4 元空间上限:-XX:MaxMetaspaceSize

JDK 8把永久代(PermGen)换成了元空间(Metaspace),一个重大变化是:默认情况下元空间没有上限,它用多少就去拿多少本地内存。这在类加载特别多的场景(热部署、动态生成类、多应用打包在一起)下,可能吃掉大量本地内存,最终导致进程被操作系统杀掉。

为什么默认不设上限?因为元空间用的是本地内存,JVM设计者觉得反正内存很大没必要限制。但生产环境从来不是单进程的,宿主机上还有其他进程,本地内存被某个JVM吃光了,大家一起遭殃。

所以建议显式设置-XX:MaxMetaspaceSize。值设多少?保守点1GB,规模大的服务可以给2GB。太小的话,运行中动态生成类多的会频繁触发Full GC,甚至OOM。另外如果你用了CGLib、ASM、反射代理这种动态生成类库,元空间要多留一些余量。

和元空间相关的还有-XX:MetaspaceSize,这个参数是触发类卸载的阈值,不是初始大小,别理解错了。它默认约20MB,但这个值只影响元空间GC触发的频率,一般不用动。

2.5 忽略一个容易爆掉的区:直接内存与代码缓存

这两个参数经常被遗忘,但出事往往就在它们身上。

  • -XX:MaxDirectMemorySize:控制堆外直接内存(DirectByteBuffer)的上限,默认等于堆大小上限。NIO、Netty用的就是这个玩意儿。不设的话,堆外内存能占用多少完全靠JVM内部算,很容易出现“堆看起来还健康,容器内存已经没了”的情况。建议显式设置,一般给堆内存的25%到50%,关键看你的Netty/Dubbo连接数和IO量。
  • -XX:ReservedCodeCacheSize:JIT编译后的本地代码缓存。默认约240MB(JDK 8开始),如果你的应用用了C2编译器并且方法非常多,代码缓存满了,JIT编译器会自动关闭继续编译,性能断崖式下跌——这个坑特别隐蔽。大型微服务建议设置到512MB。

内存这块总结成一句话:堆要固定、栈要克制、元空间要有上限、直接内存和代码缓存要心里有数。这四个维度覆盖了JVM进程内存的全部出口,任何一块失控,都不是“靠参数默认值”能兜住的。

3. 垃圾回收器与GC日志:性能的关键开关

垃圾回收是JVM里最容易被面试官问、也最容易在实际调优中出错的环节。如果没有显式指定GC算法,你的应用可能在不同JDK版本里用着完全不同的回收策略,生产环境一旦出现问题,根本没法稳定复现。

3.1 显式指定GC器:性能的“定海神针”

前面说过,JDK 8默认ParallelGC,JDK 9+默认G1。问题在于很多人并没有意识到“升版本GC行为会变”这回事。我建议所有生产环境都要显式指定:

  • 追求吞吐量、批处理、后台计算任务,选-XX:+UseParallelGC
  • 追求低延迟、响应时间优先的在线业务,选-XX:+UseG1GC
  • 如果还在用JDK 8但用的是CMS(-XX:+UseConcMarkSweepGC),尽量早点迁移——JDK 9以后CMS被废弃,JDK 14直接移除了,留着它等于给自己埋雷。

用G1的话,有几个参数也值得显式配置:

  • -XX:MaxGCPauseMillis:目标停顿时间,默认200ms。不要为了追求极端低延迟把这个值调得过小(比如10ms),G1为了达到这个目标会疯狂调小年轻代,导致GC频繁且对象晋升到老年代变多。一般设置在100ms到200ms是务实的。
  • -XX:ParallelGCThreads:并行GC线程数,默认和CPU核数相关。如果是容器环境,又没显式设置UseContainerSupport,这个值可能按宿主机核数算,导致线程数爆炸。可以配合-XX:ActiveProcessorCount一起控制。
  • -XX:ConcGCThreads:并发标记线程数,一般建议是ParallelGCThreads的1/4左右。默认值在大部分场景下够用,但如果你的对象图特别大,可以适当调大。

3.2 GC日志的正确“打开方式”

GC日志是非常关键的排障数据,能告诉你什么时候Full GC、停顿多久、晋升多少对象。不同JDK版本的GC日志参数差异特别大,这也是很多人升级之后发现日志出不来的原因:

  • JDK 8及以下:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log
  • JDK 9及以上:统一用-Xlog:gc*:file=/path/to/gc.log这种格式,旧的PrintGCDetails已经没用了。

还有几个好用的日志级别的参数:

  • -Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:输出GC日志并附带时间和JVM运行时长,排查问题时能快速对齐时间线。
  • -XX:+HeapDumpOnOutOfMemoryError配合-XX:HeapDumpPath=/opt/logs:OOM时自动导出堆转储文件,这个太重要了。没有它,OOM发生后你连现场都看不到,全靠猜。这个参数我建议无条件加上——又不要性能,纯保命。

GC日志也要做定期清理,或者依赖logrotate。很多线上事故排查时发现gc.log已经巨大无比,光打开就卡半天,别说分析了。这属于“参数配了但没配套管理”的典型教训。

3.3 GC调优的一个“反直觉”建议

很多新手调GC性能,一上来就看GC停顿时间,恨不得压到0。但实际上GC停顿时间和吞吐量是矛盾的,目标停顿时间设得太小,G1/Shenandoah会牺牲吞吐量来达到这个目标,最终结果是CPU上去了,QPS反而下来了,GC频繁到“平均停顿2ms但每分钟停100次”。

所以我调优时的顺序是:先保证不OOM,再控制Full GC频率,最后才谈单次停顿时间。如果一个服务每隔10分钟就Full GC一次,每次停顿500ms,你先别急着调MaxGCPauseMillis,先去看看老年代为什么涨这么快——是不是大对象太多?是不是连接池没复用?是不是Metaspace到了阈值触发Full GC?原因往往在代码不在GC参数。

这里也推荐一个相对冷门但实用的参数-XX:+ExitOnOutOfMemoryError。字面意思就是OOM时候直接让进程退出,让容器/进程管理器去拉起新副本。这对无状态服务其实非常合适,比“OOM后继续卡着半死不活”要健康得多。用了这个参数,配合K8s的自动重启,服务的自愈能力会强很多。

4. JIT编译与字节码控制类参数

GC管的是内存,JIT管的是“代码执行效率”。Java代码是解释执行的,热点代码会被JIT编译成机器码,这里面的参数虽然不像堆内存那样日常被讨论,但关键时刻非常有用。

4.1 编译模式:-client 与 -server 的取舍

老生常谈的-client-server。现在64位JDK基本已经忽略这两个参数了,JDK 8以后-server是默认。但如果你接手老项目,还是注意看看启动脚本:-client模式下JIT只做C1编译(轻量级优化),-server模式用C2(重度优化)。线上环境没有理由用-client,除非你明确知道自己在干什么。

4.2 分层编译与编译阈值

JDK 8开始默认开启分层编译,就是C1和C2结合使用,-XX:+TieredCompilation如果被显式关闭了,要确认你确实有理由这么干——处理不好性能会掉一大截。

编译阈值是-XX:CompileThreshold,默认是10000次方法调用触发编译。这个参数在分层编译下意义不大了,因为C1的阈值是动态调整的。除非你是做性能测试需要精确控制,否则不要动。

我实际遇到比较有用的JIT参数是-XX:-UseCounterDecay。默认JVM有“方法调用计数器热度衰减”机制,某些方法如果调用频率不够高,计数器会减下去不被编译。对于长期稳定低频调用、但一旦调用就必须快速响应的接口,关闭衰减可以让它们尽快被JIT编译,提升稳定性。这个参数在压测中尤其能体现差异。

4.3 内联优化:别跟JIT抢活

JIT有个重要的优化就是方法内联,把小方法直接嵌入调用方,避免栈帧切换开销。-XX:MaxInlineSize默认35字节,也就是小于35字节的方法会被考虑内联。很多人觉得“既然内联好,那我把这个值调大”,其实不然——内联过大会导致代码膨胀,ICache命中率下降,反而变慢。我建议信任JVM默认,不要乱调MaxInlineSize。

真正该关注的是-XX:InlineSmallCode-XX:MaxTrivialSize这类更细节的参数,但它们属于“高难度玩家专用”,普通业务没有调整必要。如果你发现某个方法执行热到怀疑人生,先用JFR(Java Flight Recorder)看看热点方法长什么样,再决定调不调编译参数。盲目调JIT参数往往是瞎忙活。

4.4 逃逸分析与栈上分配

很多人不知道,JVM的逃逸分析(Escape Analysis)是默认开启的,它会分析对象是否会被方法外部引用。如果判断对象不逃逸,可以直接在栈上分配,不需要进入堆,也就没有GC压力。这是现代JVM的一个大杀器。

有个参数-XX:+DoEscapeAnalysis可以显式开启,JDK 7以后默认就是开启的,一般不需要动。我提它,是想说一个反面教训:有些人不知道从哪看到“关闭逃逸分析可以减少GC压力”这种话——那完全是反的。关闭之后对象全跑堆上,GC频繁到起飞。不要随便关JVM默认开启的优化功能,除非你有JMH基准测试数据支撑。

5. 诊断与运维参数:出事时能救命的那几个

这部分参数平时用不上,但一旦线上出故障,它们能决定你是花半小时定位问题,还是花三天三夜拍脑袋。我见过太多生产事故,因为日志不齐全、dump文件没导出来,排障过程极其痛苦。

5.1 OOM自动导出堆转储

前面提到过,这里展开讲一下。-XX:+HeapDumpOnOutOfMemoryError是必须加的,配合-XX:HeapDumpPath指定导出路径。OOM是JVM能产生的极少数强信号,堆转储是事后分析最宝贵的“案发现场”。

需要注意路径权限问题。如果HeapDumpPath指向的目录没有写权限,JVM会尝试在启动目录写,如果也失败,dump文件就直接没了,白配。我建议专门建一个目录,比如/opt/logs/heapdump,并把挂载到外部存储,不要在容器销毁时把dump文件一起“销毁”。

5.2 打印启动参数与配置校验

-XX:+PrintCommandLineFlags,这个参数会在JVM启动时打印最终生效的显式参数和默认值。它的价值在于,启动完成后你立刻能在日志里确认JVM到底用的是你配的参数还是被覆盖成了别的。

还有一种情况:你把参数写进了JAVA_OPTS,结果应用启动脚本里又硬编码了一套参数,后者的优先级覆盖了前者。你排了半天,最后发现“配置根本没生效”,非常无语。所以打印启动参数是排查“参数没生效”类问题的第一板斧

配合-XX:+PrintFlagsFinal还能打印所有参数的最终值,可以按需过滤,比如-XX:+PrintFlagsFinal -version | grep MaxHeapSize,一条命令就能确认堆上限是多少。这个用法我几乎每次调优都用。

5.3 类加载与JVM线程的日志开关

排查类冲突、方法缺失问题,-verbose:class会打印JVM加载了哪些类、从哪个jar加载的。这个参数平时别开,日志量巨大,但调试“jar包版本不一致”“ClassNotFoundException”非常管用。

线程方面主要看线程dump。建议在启动脚本里配置好jstackjcmd这些工具的可执行路径,方便出事时快速抓取线程栈。线程dump配合GC日志、堆转储,这三样东西基本能解决99%的线上JVM问题。

还有个小参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintHeapAtGC,它会在每次GC前后打印堆内存的使用情况。对怀旧型优化和“想看GC之后内存是否真的降下来”这类场景特别有意义,但日志量也不小,生产环境视情况开启。

5.4 本地内存与轻量级监控

有些人会忽略一个名为Native Memory Tracking(NMT)的诊断功能,用-XX:NativeMemoryTracking=summary开启。它能统计堆外内存、元空间、线程栈、JIT编译器这些本地内存的占用分布。本地方向的OOM(比如此前说的直接内存爆掉),靠它是能精确定位到元凶的。

启用NMT有少量性能开销(约5%左右),考虑是否常态化开启。我的建议是:线上服务如果对性能极其敏感,可以不开;但阶段性压测、容量评估、排查内存泄漏,开一下能省很多时间。加上-XX:+PrintNMTStatistics可以退出时打印详细数据,压测结束后直接看汇总。

6. 一份可直接“抄作业”的生产参数模板

说了这么多,是时候把方案汇总成模板了。以下是我在大多数Spring Boot/Dubbo微服务场景下用的基准配置,结合了前面聊的所有要点:

JAVA_OPTS="-Xms4g -Xmx4g \ -Xss512k \ -XX:MaxMetaspaceSize=1g \ -XX:MaxDirectMemorySize=1g \ -XX:ReservedCodeCacheSize=512m \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:ParallelGCThreads=4 \ -XX:ConcGCThreads=1 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/opt/logs/heapdump \ -XX:+ExitOnOutOfMemoryError \ -XX:+PrintCommandLineFlags \ -Xlog:gc*:/opt/logs/gc.log:time,uptime,level,tags"

几个说明:

  • 堆给了4G,总内存按8G设计,留了4G给元空间、线程栈、直接内存和操作系统本身。
  • G1作为低延迟默认选项。如果你的服务是批处理或离线计算,可以换成-XX:+UseParallelGC,吞吐优先。
  • ParallelGCThreads显式设为4,是因为容器环境可能识别到宿主机核数过多,避免GC线程数失控。ConcGCThreads按1/4原则取1。
  • HeapDumpPath和GC日志都放/opt/logs下,上线前记得确认目录存在且有写权限。
  • ExitOnOutOfMemoryError适合无状态服务,如果有状态(比如跑定时任务、单机事务),谨慎使用。

这个模板不是银弹。比如堆大小就要根据具体业务调整,元空间大小也要看动态代理使用情况。但作为起步基线,比裸奔默认值稳太多了。

7. 压测与生产环境的注意事项

配置完参数不等于调优完成,上线前压测、运行中持续观察、出问题时果断回滚,这三件事一个都不能少。

7.1 压测时关注什么

你配的-Xmx4g-XX:MaxGCPauseMillis=200到底合理不合理,压测是检验的唯一标准。做压测时重点看这几个指标:

  • Full GC频率:压测过程中Full GC如果频繁出现,说明老年代压力过大,要么堆给小了,要么晋升率太高。
  • GC总停顿时间:用GC日志统计一下,压测期间GC引起的总停顿时间占比要控制在可接受范围(一般低于5%)。如果太高,说明GC开销已经肉眼可见地吃掉吞吐量了。
  • 内存泄漏前瞻:观察压测12小时、24小时的堆内存曲线,如果老年代使用量一直在缓慢爬升、不下降,小心内存泄漏。

工具上,jstat -gcutil <pid> 1000可以每秒刷新一次各区间使用率和GC次数,快捷直接。想看得更细,上jvisualvmJMC或者Arthas都行。

7.2 上线后的“黄金15分钟”

每次发布后,前15分钟我基本都在盯三个东西:GC日志是否在正常滚动、RSS内存是否稳定、Full GC有没有出现。如果你配了PrintCommandLineFlags,启动日志里会先打印一行参数,顺手确认下参数正确。

另外一个容易被坑的点:同一套参数,不同机器跑出来的内存曲线完全不一样。不是因为配置写错了,而是因为流量不同。所以不要拿测试环境的数据去推生产容量,生产的数据要以生产自身的监控为准。

7.3 参数回滚与变更管理

JVM参数变更看起来很“小”,但影响面极大——一个-Xmx改大5%,可能带来完全不同的GC行为。所以JVM参数也应该是“变更管理”的一部分,每次改动都要有记录、有理由、有回滚方案。

尤其要注意:GC参数的改动效果经常需要在长时间运行后才能看出来,短时间压测看不出区别。不要因为压测十几分钟没差别就认为某个参数没意义,这种判断太草率。

8. 常见问题与避坑技巧快查

最后整理一张快查表,把生产环境JVM参数相关的典型问题和排查思路列一下,遇到症状可以直接对号入座。

典型症状可能原因排查思路
容器内存超限被杀堆内存过大或本地内存未设上限确认-XmxMaxMetaspaceSizeMaxDirectMemorySize显式配置
启动后CPU奇高GC线程数按宿主机核数计算显式设置ParallelGCThreadsActiveProcessorCount
GC日志没有产生JDK版本升级导致日志参数失效JDK9+改用-Xlog:gc*格式
频繁Full GC但老年代不大Metaspace到达阈值触发Full GC查看日志确认,调大MaxMetaspaceSize
OOM后没有dump文件未加HeapDumpOnOutOfMemoryError或路径无权限补充参数并确认目录可写
代码缓存耗尽后性能骤降JIT停止编译调大ReservedCodeCacheSize,观察日志里“CodeCache is full”
升级JDK后响应时间恶化默认GC器变更显式指定GC算法,对比升级前后行为

几个反复踩到的坑,再啰嗦一次:

  1. -Xms当空气。有人只配了-Xmx,没配-Xms,结果堆从256M慢慢扩到4G,中间每次扩容都伴随一次Full GC。启动时全部分配到位,香得多。
  2. 在G1下强行设置-Xmn。不是说不可以,而是G1的动态调整机制会被你搞乱,建议让G1自己管年轻代。
  3. 不考虑容器限制。JDK 8u191以下版本没有容器感知能力,最好升级JDK版本,而不是靠一堆绕弯的参数去模拟容器支持。
  4. 盲目模仿网上大厂模板。阿里的参数适合阿里的场景,字节的参数适合字节的场景,你直接抄过来大概率水土不服。参数设置一定要结合自己的业务形态、流量模型、硬件条件去试。

9. 根据实际场景的经验调整

这些年踩踩爬爬,我感觉JVM参数调优一半是科学一半是艺术。所谓科学,就是遵循内存模型、GC原理这些基本规律;所谓艺术,就是面对具体业务场景做出取舍。每个服务都有自己的脾气,一个写日志的服务和一个处理订单交易的服务,JVM参数不可能完全一致。

我现在的习惯是:所有项目先套统一基线模板,然后在压测和线上监控数据的驱动下逐个调整。如果服务是典型的低延迟在线业务,就优先保证GC停顿时间可控,吞吐量可以让步;如果是离线的数据计算任务,就优先保证吞吐量,GC停顿时间反而无所谓。这就是为什么我一直强调“显式配置参数”而不是“背一堆参数值”——参数是死的,思路是活的。

在踩过无数次坑之后,我还有一个习惯:每次调整参数,都会把调整前后的GC日志和性能指标存下来。同一台机器、同一份代码、不同参数下的表现对比,是积累调优手感最重要的素材。有一些参考价值非常大的对比,比如G1默认参数和优化后参数的GC耗时对比,能直观看到不同参数配置对性能的影响。时间久了,你会形成一种直觉:看到GC曲线就大概知道哪块设置不合理,这也是调优经验的核心。希望这篇整理能帮你少走一些弯路,至少让那些“该显式配置”的参数不再裸奔。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询