8G 内存 IoT 系统的 JVM & MQ 参数调优实录
——在“小马拉大车”的现实里榨干每一 MB 性能
作者:magicxie
场景:IoT 远程打印
环境:8G 服务器 + 4G 消息中间件
目标:高并发、低延迟、不 OOM、不重复打印
前言
在上一篇文章中,我们聊了接口级故障的降级、熔断、限流和排队。但很多同学私信问我一个问题:
**“8G 内存真的够用吗?JVM 和 MQ 到底怎么配?”
说实话,在理想世界里,我会给你 32G、64G;但在现实项目里,我见过太多团队就是要在 8G 机器上硬扛几十万打印请求。
这篇文章,不讲虚的,只讲我们在生产环境验证过的 JVM 和 MQ 参数调优实录。
一、整体资源分配原则(非常重要)
在 8G 服务器上,绝对不能“All In”。
✅ 资源拆分(经验值)
|
组件 |
内存分配 |
说明 |
|---|---|---|
|
OS + 内核 |
1G |
PageCache、TCP 缓冲区 |
|
JVM(应用) |
3G |
核心业务 |
|
MQ(Broker) |
2.5G |
消息堆积、索引 |
|
预留 |
1.5G |
突发、GC、OS Cache |
⚠️ 核心原则:
JVM 永远不要用到 4G 以上,否则 MQ 和 OS 会一起“窒息”。
二、JVM 调优实录(IoT 打印场景)
1️⃣ 基础 JVM 参数(生产可用)
-Xms3g
-Xmx3g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:ParallelGCThreads=4
-XX:ConcGCThreads=2
2️⃣ 为什么选 G1?
|
GC 类型 |
是否推荐 |
原因 |
|---|---|---|
|
CMS |
❌ |
已废弃,碎片严重 |
|
Parallel GC |
⚠️ |
停顿时间长 |
|
G1 GC |
✅ |
停顿可控,适合 3G 堆 |
IoT 打印系统最怕:
-
Full GC 停顿 5s+
-
打印机回调超时
-
重复投递任务
✅ G1 能把 STW 控制在 200ms 以内
3️⃣ 关键 JVM 参数解析
✅ -XX:MaxGCPauseMillis=200
告诉 GC:你可以牺牲一点吞吐,但不能卡住接口
✅ -XX:G1HeapRegionSize=16m
-
默认是 1~32m
-
打印任务对象不大,但数量多
-
16m 是一个平衡点
✅ -XX:ParallelGCThreads=4
-
8G 机器,CPU 通常是 4C 或 8C
-
不要打满 CPU,留资源给 MQ 和网络
4️⃣ OOM 防护(非常关键)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/logs/heapdump.hprof
-XX:OnOutOfMemoryError="kill -9 %p"
📌 原因:
-
一旦发生 OOM,JVM 处于“半死不活”状态
-
不如直接 Kill,由 Supervisor / Docker 重启
5️⃣ 元空间 & 直接内存(IoT 容易忽视)
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=512m
MQ Client、Netty、NIO 都在用 Direct Memory
如果这里不设上限,OOM 会“假装在堆外”。
三、MQ(4G 环境)调优实录
假设我们使用的是 RocketMQ / Kafka 类中间件(RabbitMQ 思路类似)。
1️⃣ Broker 内存分配
-Xms2g
-Xmx2g
⚠️ 不要给 MQ 3G、4G
消息最终是靠 PageCache 读的,不是全放堆里。
2️⃣ 页缓存与刷盘策略(IoT 打印的关键)
✅ 异步刷盘(推荐)
flushDiskType=ASYNC_FLUSH
原因:
-
打印任务允许毫秒级延迟
-
同步刷盘会直接把 TPS 打残
3️⃣ 文件与内存映射
mapedFileSizeCommitLog=1G
mapedFileSizeConsumeQueue=5M
-
CommitLog 不宜过大
-
ConsumeQueue 小一点,减少内存占用
4️⃣ 消息堆积保护(非常重要)
maxMessageSize=65536
diskMaxUsedSpaceRatio=85
|
配置 |
作用 |
|---|---|
|
maxMessageSize |
防止大任务撑爆内存 |
|
diskMaxUsedSpaceRatio |
磁盘快满时拒绝写入 |
✅ IoT 打印消息体一定要精简:
{
"orderId": "...",
"deviceId": "...",
"fileId": "...",
"copies": 1
}
文件本身 不要进 MQ,只存文件 ID。
5️⃣ Consumer 端背压(4G 环境必做)
pullBatchSize=8
consumeThreadMin=10
consumeThreadMax=20
❌ 不要开 64 / 128 线程
✅ 小批量 + 慢消费 = 稳定
四、线程池 & 连接池调优(常被忽略)
1️⃣ Tomcat 线程池
server:
tomcat:
max-threads: 200
min-spare-threads: 20
accept-count: 100
-
200 是上限
-
超过就排队,而不是扩容
2️⃣ DB 连接池(HikariCP)
hikari:
maximum-pool-size: 30
minimum-idle: 5
connection-timeout: 2000
IoT 打印系统:
-
大部分压力在 MQ
-
DB 只是记账,不需要大连接池
五、操作系统层调优(8G 机器必做)
1️⃣ 文件句柄
ulimit -n 65535
MQ + Netty + 打印机长连接,句柄消耗极快。
2️⃣ TCP 参数
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1
防止大量 TIME_WAIT 占满端口。
3️⃣ 交换分区(Swap)
vm.swappiness = 10
-
完全关闭 Swap 风险高
-
设为 10,只允许“最后关头”使用
六、监控与告警(调优闭环)
✅ JVM 监控指标
-
Old Gen 使用率 > 70%
-
Full GC 次数 > 0
-
GC 停顿时间 > 500ms
✅ MQ 监控指标
-
Consumer Lag > 1000
-
Broker Disk Usage > 80%
-
Send Latency > 200ms
✅ 业务指标(最关键)
-
重复打印率
-
订单状态不一致数
-
打印机离线率
七、小结:小资源系统的生存法则
✅ 1. 不要迷信“大厂参数”
大厂 32G、64G 的参数,直接搬过来就是灾难。
✅ 2. 资源分配优先级
OS
↓
MQ
↓
JVM
↓
业务线程
✅ 3. IoT 场景的特殊性
|
通用系统 |
IoT 打印 |
|---|---|
|
允许重试 |
禁止随意重试 |
|
可回滚 |
不可回滚 |
|
内存换吞吐 |
稳定压倒一切 |
✅ 4. 一句话总结
在 8G 内存的 IoT 系统里,调优不是“压榨性能”,而是“控制欲望”。
后记
这套 JVM + MQ 参数,在我们生产环境中稳定运行了 一年多,支撑了日均 20 万+ 打印订单,未发生一次因内存问题导致的资损事故。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)