<p>Runtime.getRuntime().freeMemory() 返回的是JVM堆中已分配但未被对象占用的字节数,受GC直接影响,不反映真实可用内存;used = totalMemory() - freeMemory() 才是当前已用堆内存估算值。</p>

Runtime.getRuntime().freeMemory() 返回的到底是什么
它不是“空闲内存”,而是 JVM 堆中当前未被使用的、**已分配但尚未被对象占用**的字节数。这个值会受 GC 行为直接影响——GC 一跑,freeMemory() 可能瞬间变大;GC 没触发时,哪怕堆里全是垃圾,它也可能很小。
- 别拿它判断“内存够不够”,它不反映真实可用空间(比如没分配的堆上限部分)
- 它和
totalMemory()是配套看的:used = totalMemory() - freeMemory()才是当前已用堆内存估算值 - 如果刚启动应用就调用,
freeMemory()可能接近totalMemory(),但这不代表堆没压力——只是还没怎么分配对象
为什么 Runtime.maxMemory() 有时比 -Xmx 设置值小
maxMemory() 返回的是 JVM **实际向操作系统申请的最大堆内存上限**,不是你写的 -Xmx2g 的字面值。JVM 启动时会预留一部分空间给元空间、线程栈、JIT 编译缓存等,所以堆可用上限会被动态下调。
- 常见于容器环境:cgroup 内存限制低于
-Xmx时,JVM 会自动降低maxMemory()值以适配,避免 OOM Killer 杀进程 - Java 8u191+ 和 Java 10+ 默认启用
+UseContainerSupport,此时maxMemory()优先服从容器限制,而非 JVM 参数 - 验证方式:启动时加
-XX:+PrintGCDetails,看 GC 日志里 “max heap” 实际值是否与Runtime.getRuntime().maxMemory()一致
用 Runtime 获取内存指标的三个典型误用场景
这些写法看着合理,实则监控失真或干扰 GC:
- 在高频循环里反复调用
freeMemory()+totalMemory()计算使用率——触发频繁 GC 尝试,反而抬高延迟 - 把
maxMemory()当作“系统总内存”来对比/proc/meminfo,忽略了 native memory(如 DirectByteBuffer、JNI、JVM 自身开销)完全不计入 Runtime 统计 - 用
freeMemory() < 1024 * 1024做内存告警阈值——没考虑 GC 触发时机,可能每秒抖动几十次,告警轰炸
更靠谱的替代方案:MemoryUsage + ManagementFactory
如果真要监控堆状态,java.lang.management.MemoryUsage 提供了带时间上下文、GC 友好的快照,且不受 GC 中间态干扰:
import java.lang.management.ManagementFactory; import java.lang.management.MemoryUsage; <p>MemoryUsage heap = ManagementFactory.getMemoryMXBean() .getHeapMemoryUsage(); long used = heap.getUsed(); // 当前已用字节(含 GC 暂存对象) long max = heap.getMax(); // 当前有效最大值(等价于 maxMemory()) long committed = heap.getCommitted(); // 已向 OS 申请、可随时分配的堆大小</p>
注意:getUsed() 比 totalMemory() - freeMemory() 更稳定;getCommitted() 才是你该关注的“已落地内存”,它变化慢、可预测,适合做水位趋势分析。
复杂点在于:Runtime 的三个方法是瞬时采样,而 ManagementFactory 提供的是 GC 后快照,两者语义不同。混用它们计算“使用率”,结果可能差 20% 以上——得先想清楚你要监控的是“分配行为”还是“存活对象压力”。








