Linux 多线程应用 降低内存(VSZ)占用

本文探讨了嵌入式开发中内存紧张问题,通过分析进程内存消耗,发现主要在多线程栈上。作者尝试通过调整系统栈大小和细化到线程栈设置,将栈从8MB减小到1MB,显著减少了内存占用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

嵌入式开发的内存无疑是非常吃紧的。特别是一些老设备,之前的一台终端设备因为内存占用过高,出现了运行出错,进程管理的APP直接杀死了占用最高的媒体APP,导致视频输出出现异常。先优化一下VSZ好了。
于是进探索了内存优化之路。在确保长时间运行内存不会增加(没有内存泄漏)后,首先查看 /proc 下的进程 内存占用的smap 信息:

cat /proc/<pid>/smaps|grep -v ' 0 kB'

查看进程申请的内存详情
通过观察不难发现大量的内存占用都在栈上。由于这个APP是一个多线程进程,一个线程就有一个栈,导致了进程申请的内存大量被用于线程栈的申请上,每个栈8MB,仅10个线程就占用了100MB的虚拟内存空间。
stack mem
一个进程占用99MB内存,11个线程栈的内存占了88MB。那么方向就很明确了。减少栈的大小即可。
首先想到的是,直接进行全局的配置系统默认栈得大小,因为目前Linux程序的线程使用的都是默认的参数,即采用系统默认的栈大小进行开辟线程的栈。使用ulimit -s 指令在启动脚本加入:

ulimit -s 1024

以上就将系统默认栈设置为1024KB,单位是KB。启动系统,不巧的是,有些APP运行出现了异常,猜测界面APP使用了大量的变量,导致栈的溢出,APP直接就退出了。缩小系统栈还是有一定的风险,而且不知会不会引入其他的问题。
想着既然Linux系统一般都是使用pthread启动线程,而且子线程一般变量较少,不需要很大的栈空间,那么我们可以通过pthread_create的pthread_attr_t进行参数的设定设定进程生成的线程栈,达到缩小进程内存占用的目的。
将佘艳的线程生成

int thread::startThread(callable *c) {

	int const res = pthread_create(&handle_, 0, &posix_thread_proxy, static_cast<void*>(c));
	if (res != 0) {
		delete c;
		return -1;
	}

	return 0;
}

改为这样创建线程(这里的stacksize的单位是字节)

int thread::startThread(callable *c) {

	pthread_attr_t thread_attr;
	pthread_attr_init(&thread_attr);
	pthread_attr_setstacksize(&thread_attr, 1024*1024); ///< 1MB
	int const res = pthread_create(&handle_, &thread_attr, &posix_thread_proxy, static_cast<void*>(c));
	if (res != 0) {
		delete c;
		return -1;
	}
	pthread_attr_destroy(&thread_attr);

	return 0;
}

即可将原来的8MB栈大小改为1MB,效果显著。

top指令查看 左边为修改前,右侧为修改后
top
ps -o pid,comm,rss,vsz 指令查看 左边为修改前,右侧为修改后
ps
可以看到线程多的APP占用基本缩小了一半多。

<think>好的,我需要帮助用户解决如何通过Linux命令检查运行在Kubernetes Pod中的JVM内存使用情况和堆外内存占用的问题。首先,我得回忆一下相关的Linux命令和JVM内存管理的知识。 用户提到了Kubernetes Pod,所以可能需要用到kubectl命令来进入容器或者获取容器信息。比如使用kubectl exec进入Pod执行命令。另外,用户需要查看JVM内存,这涉及到JVM自身的堆内存和非堆内存(堆外内存)。这时候可能需要JVM提供的工具,比如jstat或者jcmd,这些工具可以查看堆内存的使用情况,比如Eden区、Survivor区、老年代的使用量。 不过,用户还关心堆外内存的使用。这部分可能包括Native Memory,比如直接内存(Direct Buffer)、线程栈、元空间(Metaspace)等。JVM本身可能不会完全统计这些,所以可能需要结合操作系统的工具来查看进程的内存占用情况。常用的命令有top、ps、pmap等,特别是查看RSS(Resident Set Size)和VSZ(Virtual Memory Size)。但需要注意的是,容器的内存限制可能会影响这些命令的显示,因为在容器内看到的可能不是宿主机的真实数据。 另外,引用中提到的内容也需要注意。根据引用[2],JVM会预先从操作系统分配内存,导致容器级别的RSS较,而JVM内部的堆和非堆内存可能并未达到限制。所以用户可能需要同时查看JVM内部的内存使用情况和整个Pod的内存占用情况。 接下来需要整理具体的步骤和命令。比如,进入Pod后,可以使用jstat -gc <pid>来查看堆内存各个区域的使用情况。而堆外内存可能需要使用jcmd <pid> VM.native_memory来查看,但需要JVM启动时开启Native Memory Tracking(NMT)。此外,通过top或ps命令查看进程的RSS,结合容器的内存限制,可以判断是否有内存泄漏或者过度分配的情况。 还有可能需要检查JVM参数,比如-Xmx和-Xms的设置,以及是否使用了某些会占用堆外内存的库或框架,比如Netty使用的直接内存缓冲。这时候可能需要结合jmap或其他工具来生成堆转储,分析内存分布。 总结一下,步骤可能包括: 1. 使用kubectl进入Pod。 2. 查找Java进程的PID。 3. 使用jstat查看堆内存统计。 4. 启用NMT后使用jcmd查看Native Memory。 5. 使用top/ps查看进程内存占用。 6. 分析容器的内存指标,比如通过kubectl top pod。 同时需要提醒用户关于NMT的性能影响,以及如何正确配置JVM参数来限制堆外内存的使用,避免容器被OOMKilled。可能还需要结合容器的内存监控工具,如Prometheus,来长期监控内存使用趋势。 现在需要把这些思路整理成结构化的步骤,并确保引用了提供的参考资料中的内容,比如引用[2]和引用[3]提到的JVM预分配内存和排查方法。此外,要确保命令正确,并给出示例,方便用户直接使用。最后生成相关问题,帮助用户深入理解问题。</think>### 通过Linux命令检查Pod中JVM内存及堆外内存使用的方法 #### 1. 进入Kubernetes Pod环境 使用`kubectl exec`命令进入目标Pod: ```bash kubectl exec -it <pod名称> -- /bin/bash ``` #### 2. 查找Java进程PID 通过`ps`命令获取JVM进程的PID: ```bash ps aux | grep java ``` 输出示例: ``` root 1 5.2 2.1 1234567 65432 ? Sl 10:00 java -Xmx512m -jar app.jar ``` #### 3. 查看JVM堆内存使用 使用`jstat`命令监控堆内存分区使用情况: ```bash jstat -gc <PID> 1000 5 # 每秒采样1次,共5次 ``` 输出示例: ``` S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT 512.0 512.0 0.0 256.0 4096.0 2048.0 10240.0 5120.0 2048.0 1536.0 256.0 128.0 5 0.123 1 0.456 0 0.000 0.579 ``` 字段说明: - `EC/EU`:Eden区容量/使用量 - `OC/OU`:老年代容量/使用量 - `MC/MU`:元空间容量/使用量 #### 4. 分析堆外内存(需启用NMT) **步骤1**:启动JVM时添加参数: ```bash -XX:NativeMemoryTracking=detail ``` **步骤2** 查看原生内存分布: ```bash jcmd <PID> VM.native_memory summary ``` 输出示例: ``` Total: reserved=1456234KB, committed=456234KB - Java Heap (reserved=524288KB, committed=262144KB) - Class (reserved=234567KB, committed=12345KB) - Thread (reserved=12345KB, committed=2345KB) - Code (reserved=45678KB, committed=12345KB) - GC (reserved=34567KB, committed=12345KB) - Internal (reserved=2345KB, committed=1234KB) - Other (reserved=12345KB, committed=2345KB) ``` #### 5. 操作系统级内存监控 使用`pmap`分析进程内存映射: ```bash pmap -x <PID> | sort -nk3 | tail ``` 输出示例: ``` Address Kbytes RSS Dirty Mode Mapping 00007f8d90000000 1048580 456780 123456 rw-s [anon] ``` 重点关注`[anon]`段的大内存块,可能对应堆外内存分配[^2]。 #### 6. 容器内存监控 使用`kubectl top`查看Pod整体内存消耗: ```bash kubectl top pod <pod名称> ``` 输出示例: ``` NAME CPU(cores) MEMORY(bytes) app-pod 350m 876Mi ``` #### 关键问题排查点 1. **堆内存泄漏**:持续观察`jstat`输出,若`OU`(老年代使用量)持续增长且频繁触发FGC后不下降 2. **元空间泄漏**:`MC/MU`持续增长,可能由动态类生成导致 3. **直接内存泄漏**:NMT中`Internal`或`Other`部分异常增长,常见于NIO的DirectBuffer使用 4. **线程泄漏**:通过`jstack <PID> | grep 'java.lang.Thread.State' | wc -l`统计线程数 ### 典型问题解决案例 某Pod频繁OOMKilled但JVM堆使用正常,通过以下步骤排查: 1. `jstat`显示堆内存使用稳定在300MB(Xmx=512MB) 2. `pmap`发现多个100MB+的匿名内存映射 3. `jcmd VM.native_memory`显示`Internal`部分占用800MB 4. 最终定位到JNI库存在内存泄漏,修复后内存稳定[^3]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值