19人参与 • 2026-04-26 • Linux
在现代 linux 系统中,swap(交换空间)作为物理内存的补充,在系统内存不足时发挥着“安全网”的作用。然而,频繁使用 swap 会导致严重的性能下降 —— 因为磁盘 i/o 的速度远远低于 ram。对于运行 java 应用程序的服务端环境来说,swap 的过度使用可能意味着 gc 停顿时间变长、响应延迟增加、甚至服务不可用。
本篇博客将深入探讨 linux 内存管理机制,分析 swap 被频繁触发的原因,并提供一系列实用策略来减少 swap 使用,从而提升系统整体性能。我们还将结合 java 应用场景,给出具体的代码示例和调优建议。
swap 本质上是硬盘上的一块预留区域(或文件),当物理内存(ram)不够用时,内核会把部分“不活跃”的内存页移动到 swap 中,以腾出空间给更需要的进程。这个过程叫做“换出”(swap out);当这些数据再次被访问时,系统又必须将其从 swap 加载回内存,称为“换入”(swap in)。
实测案例:某电商大促期间,一台 16gb 内存的服务器因 jvm 堆设置过大 + 其他进程占用,导致频繁 swap,最终接口平均响应时间从 50ms 飙升至 2s+。
要优化 swap,首先要理解 linux 如何管理内存。
linux 使用“虚拟内存”机制,每个进程看到的是独立的地址空间,由内核负责映射到物理内存或 swap。内核维护一个“页面回收器”(page reclaim),根据 lru(最近最少使用)等算法决定哪些页面可以被换出。
关键概念:
在动手优化前,我们需要知道当前系统的 swap 使用状况。
# 查看内存和 swap 使用概况
free -h
# 查看各进程 swap 占用
for file in /proc/*/status ; do
awk '/vmswap|name/{printf $2 " " $3}end{ print ""}' $file
done | sort -k 2 -n -r | head -10
# 实时监控 swap i/o
vmstat 1
# 查看详细内存统计
cat /proc/meminfo
total used free shared buff/cache available mem: 15gi 8.2gi 1.1gi 345mi 6.3gi 6.7gi swap: 2.0gi 1.8gi 200mi
这里可以看到 swap 几乎被用满 —— 这是一个危险信号!
linux 内核通过 swappiness 参数控制 swap 的“积极性”。
# 查看当前 swappiness 值(默认通常是 60) cat /proc/sys/vm/swappiness
很多人认为“设为 0 就完全不用 swap”,这是错误的!
swappiness=0 只是在有足够空闲内存时不主动换出匿名页,但在内存严重不足时仍会触发 swap 或 oom killer。
对于大多数 java 服务器,建议将 swappiness 设置为 1~10 之间。
# 临时生效 sudo sysctl vm.swappiness=1 # 永久生效(写入配置文件) echo 'vm.swappiness=1' | sudo tee -a /etc/sysctl.conf sudo sysctl -p
推荐值:
- 数据库服务器、java 应用服务器:1
- 桌面系统、开发机:30~60
- 内存密集型计算节点:0

这张图清晰展示了:内存压力 → 缓存回收失败 → swap 或 oom 的路径。我们的目标就是阻止流程走到 swap 或 oom。
linux 会尽可能利用空闲内存做文件缓存(page cache),这对 i/o 性能有益,但在内存紧张时可能“占着茅坑不拉屎”。
你可以手动清理缓存(谨慎操作):
# 清理 pagecache echo 1 > /proc/sys/vm/drop_caches # 清理 dentries 和 inodes echo 2 > /proc/sys/vm/drop_caches # 清理所有(pagecache + dentries + inodes) echo 3 > /proc/sys/vm/drop_caches
警告:生产环境慎用!会导致后续文件读取变慢(需重新加载到缓存)。
更推荐的方法是让系统自动管理,或通过限制 jvm 堆大小 + 监控来避免内存耗尽。
java 应用的内存不仅包括堆(heap),还包括:
这些都占用 非堆内存(off-heap),容易被忽视。
public class memoryfootprintdemo {
public static void main(string[] args) throws exception {
// 设置 jvm 参数:-xmx2g -xms2g -xx:maxmetaspacesize=256m
system.out.println("jvm 最大堆内存: " +
runtime.getruntime().maxmemory() / 1024 / 1024 + " mb");
system.out.println("jvm 总内存: " +
runtime.getruntime().totalmemory() / 1024 / 1024 + " mb");
system.out.println("jvm 空闲内存: " +
runtime.getruntime().freememory() / 1024 / 1024 + " mb");
// 创建一些对象模拟内存使用
list<byte[]> list = new arraylist<>();
for (int i = 0; i < 1000; i++) {
list.add(new byte[1024 * 1024]); // 1mb each
thread.sleep(10);
}
system.gc(); // 建议gc(不一定立即执行)
thread.sleep(1000);
system.out.println("gc后空闲内存: " +
runtime.getruntime().freememory() / 1024 / 1024 + " mb");
}
}编译运行:
javac memoryfootprintdemo.java java -xmx2g -xms2g -xx:maxmetaspacesize=256m memoryfootprintdemo
虽然你设置了 -xmx2g,但实际进程占用可能达到 2.5gb 甚至更高,因为还有栈、元空间、本地内存等开销。
不要只看 -xmx!一个经验公式:
总内存 ≈ xmx + maxmetaspacesize + (线程数 × 栈大小) + directmemory + 本地库 + 安全余量
-xmx4g -xx:maxmetaspacesize=512m -xx:threadstacksize=1m 线程数:200 -xx:maxdirectmemorysize=1g(默认等于 xmx) 估算: 4g (堆) + 0.5g (元空间) + 200 × 1m = 0.2g (栈) + 1g (直接内存) + 0.5g (jni/本地库/安全缓冲) ≈ 6.2 gb
因此,如果你的机器只有 8gb 内存,跑这样一个 jvm 是非常危险的 —— 极易触发 swap。
# 不要贪心!留足空间给操作系统和其他进程 java -xmx3g -xms3g myapp
# 避免类加载过多撑爆内存 java -xx:maxmetaspacesize=256m myapp
# netty、nio 等框架常用 directbuffer,需显式限制 java -xx:maxdirectmemorysize=512m myapp
# 默认 1mb,对多数应用过大 java -xss256k myapp
transparent huge pages(thp)是 linux 的一项内存优化技术,旨在减少页表项、提高 tlb 命中率。但对于 java 应用(尤其是使用 g1/zgc 的场景),thp 可能导致内存分配延迟、swap 行为异常。
cat /sys/kernel/mm/transparent_hugepage/enabled
输出如:
[always] madvise never
表示当前为 always 模式 —— 对 java 不友好!
# 临时关闭 echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag # 永久生效(加入 rc.local 或 systemd 服务) echo 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' | sudo tee -a /etc/rc.local echo 'echo madvise > /sys/kernel/mm/transparent_hugepage/defrag' | sudo tee -a /etc/rc.local
如果你在 docker/kubernetes 中运行 java 应用,强烈建议使用 cgroups 限制内存,避免某个容器吃光宿主机内存导致全局 swap。
docker run -it --memory=4g --memory-swap=4g openjdk:17 java -xmx3g myapp
--memory=4g:容器最大可用内存--memory-swap=4g:swap 上限(设为与 memory 相等即禁用额外 swap)resources:
limits:
memory: "4gi"
requests:
memory: "3gi"同时,在 jvm 中配合使用:
java -xx:+usecontainersupport -xx:maxrampercentage=75.0 myapp
这样 jvm 会根据容器内存限制自动调整堆大小。
频繁的 full gc 会导致内存剧烈波动,可能触发内核的内存回收机制,间接增加 swap 风险。
java -xx:+useg1gc -xx:maxgcpausemillis=200 myapp
java -xx:+usezgc -xmx4g myapp
java -xx:+useshenandoahgc -xmx4g myapp
public class gctest {
private static final int size = 10000;
private static list<byte[]> cache = new arraylist<>();
public static void main(string[] args) throws interruptedexception {
system.out.println("开始内存压力测试...");
for (int i = 0; i < 100; i++) {
simulateworkload();
thread.sleep(1000);
}
}
private static void simulateworkload() {
// 分配大量临时对象
for (int i = 0; i < size; i++) {
cache.add(new byte[1024 * 10]); // 10kb each
}
// 偶尔保留一些对象模拟缓存
if (cache.size() > size * 50) {
cache.sublist(0, size * 30).clear();
}
system.out.println("当前缓存大小: " + cache.size());
}
}分别使用以下参数运行,观察 top 或 htop 中的 res(常驻内存)变化:
# parallel gc(传统,易波动) java -xx:+useparallelgc gctest # g1 gc(较平稳) java -xx:+useg1gc gctest # zgc(最平稳,适合大堆) java -xx:+usezgc gctest
你会发现 zgc 的内存曲线最平滑,不容易触发内核的激进回收行为。
没有监控的优化都是耍流氓!
used_swap / total_swap 比率si / so(vmstat 中的 swap-in/out)#!/bin/bash
# check_swap.sh
swap_used=$(free | grep swap | awk '{print $3}')
swap_total=$(free | grep swap | awk '{print $2}')
if [ $swap_total -eq 0 ]; then
echo "no swap configured."
exit 0
fi
swap_percent=$((swap_used * 100 / swap_total))
if [ $swap_percent -gt 30 ]; then
echo "🚨 高 swap 使用率: ${swap_percent}%"
# 可集成邮件、企业微信、钉钉等告警
# send_alert "swap usage is ${swap_percent}% on $(hostname)"
fi添加到 crontab 每分钟检查:
* * * * * /path/to/check_swap.sh >> /var/log/swap_monitor.log 2>&1
有时候,最好的优化是“不用优化”——从架构上降低内存压力。
// ❌ 不推荐:在 jvm 堆内缓存大量数据 map<string, userdata> usercache = new concurrenthashmap<>(); // ✅ 推荐:使用 redis/memcached redistemplate<string, userdata> redistemplate;
import org.apache.commons.pool2.basepooledobjectfactory;
import org.apache.commons.pool2.pooledobject;
import org.apache.commons.pool2.impl.defaultpooledobject;
import org.apache.commons.pool2.impl.genericobjectpool;
import org.apache.commons.pool2.impl.genericobjectpoolconfig;
public class bytebufferpool {
private genericobjectpool<bytebuffer> pool;
public bytebufferpool(int maxsize) {
genericobjectpoolconfig<bytebuffer> config = new genericobjectpoolconfig<>();
config.setmaxtotal(maxsize);
config.setblockwhenexhausted(true);
config.setmaxwaitmillis(3000);
pool = new genericobjectpool<>(new bytebufferfactory(), config);
}
public bytebuffer borrow() throws exception {
return pool.borrowobject();
}
public void release(bytebuffer buffer) {
buffer.clear();
pool.returnobject(buffer);
}
static class bytebufferfactory extends basepooledobjectfactory<bytebuffer> {
@override
public bytebuffer create() {
return bytebuffer.allocatedirect(1024); // 1kb direct buffer
}
@override
public pooledobject<bytebuffer> wrap(bytebuffer buffer) {
return new defaultpooledobject<>(buffer);
}
}
}对象池减少了频繁创建/销毁对象带来的 gc 压力,间接降低内存峰值。
听起来像废话?但很多时候,加内存是最经济高效的方案。
经验法则:
如果你的服务器 swap 使用率长期 > 10%,且无法通过软件优化解决 —— 是时候加内存了!
我们在一台 8gb 内存的 ubuntu 22.04 服务器上部署了一个 spring boot 应用,进行压测对比。
swappiness=60thp=always-xmx6gswappiness=1thp=madvise-xmx4g -xx:maxdirectmemorysize=512m -xx:+useg1gcab -n 10000 -c 100 http://localhost:8080/api/heavy
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 平均响应时间 | 1200ms | 85ms |
| swap 使用 | 1.8gb | 0mb |
| full gc 次数 | 27 | 2 |
| 系统 load | 8.5 | 1.2 |
| 服务可用性 | 87% | 99.99% |
效果立竿见影!
zram 是 linux 内核的一项技术,它在内存中创建压缩块设备作为 swap,相比磁盘 swap 速度快得多。
sudo apt install zram-config sudo systemctl restart zram-config
# 创建 2gb zram 设备 echo 2147483648 > /sys/class/zram-control/hot_add zram_dev=/dev/zram$(cat /sys/class/zram-control/hot_add) # 初始化并启用 mkswap $zram_dev swapon $zram_dev # 查看状态 zramctl
zram 特别适合内存小但 cpu 强的设备(如树莓派、云函数、边缘节点)。
没有 swap 的系统在内存不足时会直接触发 oom killer,可能导致关键进程被杀,服务完全不可用。swap 是安全气囊,不是敌人。
堆越大,gc 停顿越长,内存回收效率越低,更容易触发系统级内存回收(包括 swap)。适度才是王道。
容器只是隔离,不是无限资源。不设 limit 的容器照样能把宿主机拖垮。
还要关注 si/so(换入换出速率)、pgpgin/pgpgout(页面 i/o)、commit limit 等指标。
vm.swappiness=1madvise)作为 java 开发者,你不仅要写好业务代码,还要:
记住:再优雅的代码,跑在频繁 swap 的机器上,用户体验也是灾难性的。
减少 swap 使用不是一蹴而就的任务,而是一个持续监控、调优、反馈的过程。通过合理设置系统参数、优化 jvm 配置、改进应用架构,我们可以最大限度地发挥物理内存的性能,避免磁盘 i/o 成为系统瓶颈。
特别是在云原生时代,资源精细化运营变得尤为重要。每一 mb 内存的节省,都可能带来成本的降低和服务质量的提升。
以上就是linux内存管理与减少swap使用的完整指南的详细内容,更多关于linux内存管理与swap减少使用的资料请关注代码网其它相关文章!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论