3、JVM调优基础:堆内存与栈内存配置、GC算法选择、JVM参数调优实战
说到ThingsBoard的性能调优,JVM这块是绕不开的硬骨头。我刚开始接触ThingsBoard时,也踩过不少坑——明明服务器配置不差,但系统就是卡顿,时不时还来个Full GC把服务搞挂。后来才发现,问题出在JVM参数上。
这一章,咱们就聊聊JVM调优的基础。说白了,就是搞清楚堆内存和栈内存怎么分,GC算法怎么选,以及那些关键的JVM参数到底该怎么配。
3.1 堆内存(Heap)与栈内存(Stack)配置
先说说堆内存。ThingsBoard作为一个物联网平台,要处理大量设备数据、规则链执行、告警计算等任务。这些对象都住在堆里。堆配小了,频繁GC;配大了,又浪费资源。
堆内存的核心配置
我个人习惯,先看ThingsBoard的负载类型。如果是纯消息转发场景,堆内存可以小一些;如果规则链复杂、有大量聚合计算,堆就得给足。
# 典型的ThingsBoard JVM堆配置
-Xms4g # 初始堆大小
-Xmx8g # 最大堆大小
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
这里有个经验值:堆内存不要超过物理内存的70%。为什么?因为操作系统、其他进程、还有堆外内存都需要空间。我曾经在一台16G的服务器上把堆设成了12G,结果系统频繁SWAP,性能反而更差。
堆内存分配建议(基于ThingsBoard场景)
| 设备数量 | 规则链复杂度 | 建议堆大小 |
|---|---|---|
| < 1万 | 简单 | 2G - 4G |
| 1万 - 10万 | 中等 | 4G - 8G |
| 10万 - 50万 | 复杂 | 8G - 16G |
| > 50万 | 高复杂度 | 16G - 32G(建议集群) |
栈内存(Stack)配置
栈内存容易被忽略。ThingsBoard的规则链执行是深度递归的,特别是嵌套的脚本节点。如果栈配小了,很容易抛出 StackOverflowError。
-Xss512k # 每个线程的栈大小
嗯,这里要注意:栈不是越大越好。栈越大,能创建的线程数就越少。ThingsBoard的Netty线程池、规则链执行线程、定时任务线程,加起来可能上百个。我建议栈大小设在256k到1m之间,根据规则链的嵌套深度来调整。
小技巧: 如果你发现规则链执行时频繁报StackOverflowError,先别急着加栈大小。检查一下规则链是不是有循环引用,或者脚本节点是不是写了死递归。我曾经遇到一个客户,一个规则链嵌套了20多层,栈加到2m才解决——后来发现是设计问题。
3.2 GC算法选择:G1还是ZGC?
GC算法的选择,直接影响ThingsBoard的响应时间。你想想看,设备上报数据时,如果正好赶上Full GC,那延迟就上去了。
G1 GC:成熟稳定的选择
G1是JDK 11及以后版本的默认GC。它把堆分成多个Region,能控制GC暂停时间。对于ThingsBoard这种需要平衡吞吐量和延迟的场景,G1是个好选择。
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 目标暂停时间200ms
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发GC的堆占用阈值
我个人习惯把 MaxGCPauseMillis 设在100-300ms之间。设得太小,GC会过于频繁,反而降低吞吐量。
ZGC:低延迟的利器
如果你用的是JDK 17+,而且对延迟要求极高(比如毫秒级响应),ZGC值得一试。ZGC的暂停时间基本在10ms以内,几乎不影响业务。
-XX:+UseZGC
-XX:ConcGCThreads=2 # 并发GC线程数
-XX:ParallelGCThreads=4 # 并行GC线程数
不过,ZGC也有代价。它更吃CPU,而且堆内存越大,效果越好。我建议堆在8G以上再考虑ZGC。
避坑指南: 我曾经在一个4G堆的ThingsBoard实例上试过ZGC,结果CPU飙到90%以上,GC倒是快了,但业务处理能力反而下降了。所以,ZGC不是万能的,要根据场景选。
GC算法对比
| 特性 | G1 GC | ZGC |
|---|---|---|
| JDK版本 | JDK 9+ | JDK 15+(实验性)/ JDK 17+(正式) |
| 暂停时间 | 100-300ms | < 10ms |
| CPU开销 | 中等 | 较高 |
| 堆内存建议 | 4G - 16G | 8G以上 |
| 适用场景 | 通用场景,平衡吞吐和延迟 | 低延迟敏感场景 |
3.3 JVM参数调优实战
理论知识说完了,咱们来点实际的。下面是我在多个ThingsBoard生产环境中验证过的一套JVM参数模板。
# ThingsBoard JVM调优参数模板(8G堆,G1 GC)
-server
-Xms8g
-Xmx8g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
-Xss512k
# GC配置
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1HeapRegionSize=4m
-XX:G1NewSizePercent=10
-XX:G1MaxNewSizePercent=30
# GC日志(用于排查问题)
-Xlog:gc*:file=/var/log/thingsboard/gc.log:time,uptime,level,tags:filecount=10,filesize=10m
# 内存溢出时自动dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/thingsboard/heapdump.hprof
# 其他优化
-XX:+DisableExplicitGC
-XX:+UseStringDeduplication
-Djava.awt.headless=true
参数解读
-XX:G1HeapRegionSize=4m:Region大小设为4m,适合ThingsBoard这种对象大小不一的场景。太小了Region数量多,管理开销大;太大了大对象分配容易浪费。-XX:G1NewSizePercent=10和-XX:G1MaxNewSizePercent=30:控制年轻代大小。ThingsBoard的规则链执行会产生大量临时对象,年轻代太小会导致频繁Minor GC。-XX:+DisableExplicitGC:禁用显式GC调用。有些第三方库会调用System.gc(),这个参数可以防止它们触发Full GC。-XX:+UseStringDeduplication:去重重复的字符串。ThingsBoard的设备名称、数据类型等字符串重复率很高,这个参数能有效减少堆内存占用。
调优步骤建议:
- 先用默认参数跑一周,收集GC日志和性能指标。
- 分析GC频率和暂停时间,确定瓶颈。
- 逐步调整参数,每次只改1-2个,观察效果。
- 不要一次性把所有参数都改了,否则出了问题你都不知道是哪个参数导致的。
实战案例
我记得有一次,一个客户的ThingsBoard集群在设备量达到5万时,频繁出现Full GC,每次暂停时间超过5秒。我上去一看,堆内存设了16G,但 InitiatingHeapOccupancyPercent 用的是默认值45。问题出在哪里?
原来,他们的规则链里有个节点会缓存大量设备状态数据,导致老年代增长很快。45%的阈值太低了,G1过早开始并发标记,但标记过程中又有大量新对象进入老年代,最终触发Full GC。
我把 InitiatingHeapOccupancyPercent 调到了60,同时把 G1HeapRegionSize 从默认的1m调到了4m。调整后,Full GC消失了,暂停时间稳定在150ms左右。
注意: JVM参数调优没有银弹。同样的参数,在不同的硬件、不同的负载下,效果可能完全不同。一定要基于实际监控数据做调整,不要照搬别人的配置。
好了,这一章的内容就到这里。下一章我们会聊聊ThingsBoard的数据库调优,特别是Cassandra和PostgreSQL的配置优化。到时候见。
公众号:蓝海资料掘金营,微信deep3321