基于Java实现查看进程内存占用情况
作者:南城花随雪。
1. 项目背景与介绍
在系统监控和性能调优中,了解各个进程的内存占用情况是非常重要的一环。通过查看进程内存使用情况,开发者和运维人员可以及时发现异常进程、资源瓶颈和内存泄漏问题。虽然操作系统本身通常提供了相应的命令或工具(如 Windows 的 tasklist、Linux 的 ps 等),但在 Java 应用中实现一个跨平台的进程内存监控工具,可以方便地将监控结果集成到系统管理平台或应用程序中。
本项目旨在使用 Java 编写一个简单的程序,通过调用操作系统的命令来获取系统中各个进程的内存使用情况,并将结果输出到控制台。通过本项目,读者可以了解到如何利用 Java 的 ProcessBuilder 调用外部命令,如何解析命令输出,并掌握一些跨平台的判断技巧。
2. 相关知识
2.1 操作系统命令
不同平台下查看进程内存占用的命令有所不同:
- Windows:常用的命令是
tasklist,可以列出所有进程及其内存使用情况。 - Linux/Unix/Mac:可以使用
ps aux命令来查看各进程的详细信息,其中包含内存占用百分比等数据。
2.2 ProcessBuilder 与 Process
Java 提供的 ProcessBuilder 类可以用来启动一个外部进程,执行操作系统命令,并通过返回的 Process 对象获取该进程的输出信息。利用该机制,我们可以在 Java 程序中调用 tasklist 或 ps aux 命令来获取进程信息。
2.3 跨平台判断
通过调用 System.getProperty("os.name"),我们可以获取当前操作系统的名称。根据返回结果判断操作系统类型,从而选择合适的命令进行调用。
3. 项目实现思路
本项目实现查看进程内存占用情况的主要思路如下:
- 判断操作系统类型:使用
System.getProperty("os.name")判断当前系统是 Windows 还是 Linux/Unix/Mac,然后选择合适的命令。 - 构造并启动外部进程:利用
ProcessBuilder构造命令(如 Windows 下的cmd /c tasklist,Linux/Mac 下的bash -c "ps aux"),并启动进程。 - 读取并输出命令执行结果:从外部进程的标准输出中读取内容,然后将结果显示在控制台。也可以进一步对结果进行解析和处理(例如提取内存使用量等信息)。
- 处理异常并确保资源关闭:捕获并处理可能出现的 I/O 异常和中断异常,确保程序健壮运行。
4. 完整代码实现
下面是一份完整的 Java 代码示例,利用 ProcessBuilder 调用系统命令获取进程信息,并输出结果。代码中包含了详细的中文注释,便于理解每个步骤的实现原理。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* ProcessMemoryUsage 类实现了一个简单的 Java 程序,用于查看系统中各进程的内存占用情况。
* 程序根据当前操作系统选择合适的命令(Windows 下使用 tasklist,Linux/Mac 下使用 ps aux),
* 通过 ProcessBuilder 调用外部命令,并将命令输出结果打印到控制台。
*/
public class ProcessMemoryUsage {
public static void main(String[] args) {
// 获取当前操作系统名称,并转换为小写
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder processBuilder;
// 根据操作系统类型构造不同的命令
if (os.contains("win")) {
// Windows 系统下,使用 "cmd /c tasklist" 命令获取进程信息
processBuilder = new ProcessBuilder("cmd", "/c", "tasklist");
} else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) {
// Linux/Unix/Mac 系统下,使用 "bash -c ps aux" 命令获取进程信息
processBuilder = new ProcessBuilder("bash", "-c", "ps aux");
} else {
System.out.println("当前操作系统不支持该程序:" + os);
return;
}
try {
// 启动外部进程
Process process = processBuilder.start();
// 获取外部进程的标准输出流
InputStream inputStream = process.getInputStream();
// 使用 BufferedReader 读取输出内容
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
System.out.println("进程内存占用情况:");
// 逐行读取输出,并打印到控制台
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 等待外部进程执行完毕
int exitCode = process.waitFor();
System.out.println("命令执行完毕,退出码:" + exitCode);
} catch (IOException | InterruptedException e) {
// 捕获异常,并打印错误堆栈信息
System.err.println("执行命令时发生错误:" + e.getMessage());
e.printStackTrace();
}
}
}
5. 代码解读
5.1 操作系统判断
获取操作系统名称:使用 System.getProperty("os.name").toLowerCase() 获取当前系统名称,并转换为小写,便于后续判断。
构造命令,根据操作系统判断:
- 如果系统名称中包含 “win”,则认为是 Windows,使用
"cmd", "/c", "tasklist"作为命令。 - 如果系统名称包含 “nix”、“nux” 或 “mac”,则使用
"bash", "-c", "ps aux"命令获取进程信息。 - 若不支持当前系统,则输出提示信息并退出程序。
5.2 启动外部进程与读取输出
ProcessBuilder 启动进程:通过 processBuilder.start() 启动外部命令对应的进程。
读取输出:获取进程的标准输出流,并利用 BufferedReader 按行读取输出内容,然后打印到控制台。这样可以直观地查看各进程的内存占用、PID、进程名称等信息。
等待进程结束:调用 process.waitFor() 方法等待外部进程执行完毕,并获取退出码,便于了解命令执行状态。
5.3 异常处理
程序中使用 try-catch 捕获 IOException 和 InterruptedException 异常,并打印详细错误信息,确保程序在出现异常时不会崩溃,并方便调试。
6. 项目总结与展望
本项目通过 Java 实现了查看系统中各进程内存占用情况的功能。主要收获与体会包括:
掌握调用外部命令的基本方法:通过 ProcessBuilder 启动外部进程并读取输出,使得 Java 程序能够调用操作系统自带的命令行工具,实现跨平台的系统监控。
跨平台处理技巧:通过判断操作系统类型,选择不同的命令执行,实现了 Windows 与 Linux/Mac 平台下的兼容性。
资源管理与异常处理:合理处理进程输出流、等待进程结束以及捕获异常,确保程序健壮运行。
扩展与优化方向
- 结果解析与展示:可以进一步解析命令输出,提取出各个进程的内存使用数据,并将其格式化展示,例如生成图表或排序展示内存占用最大的进程。
- 图形界面集成:结合 Java Swing 或 JavaFX,将进程内存占用数据展示在图形界面上,构建一个实时监控工具。
- 定时刷新:实现定时刷新功能,周期性更新系统进程信息,实现动态监控。
总之,本项目展示了如何利用 Java 调用外部命令查看系统进程信息,并为后续构建更复杂的系统监控工具提供了基础。希望这篇博客文章能为你在 Java 系统监控与性能调优方面提供有价值的参考和启发。
7.方法补充
方案一:读取 /proc/[pid]/status(Linux 推荐,最准确)
Linux 内核会把进程的实际物理内存写入 /proc/[pid]/status 的 VmRSS 字段,它与 top 的 RES 一致。这是 Linux 上获取 RSS 最直接、开销最低的方式。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public static long getRssFromProc(long pid) throws IOException {
String statusFile = "/proc/" + pid + "/status";
return Files.readAllLines(Paths.get(statusFile))
.stream()
.filter(line -> line.startsWith("VmRSS:"))
.map(line -> line.split("\\s+")[1].trim())
.mapToLong(Long::parseLong)
.findFirst()
.orElse(-1L); // 单位:KB
}优势:不依赖外部命令,不受 ps 输出格式变化影响,读取一个文本文件的开销极低。
局限:仅适用于 Linux。在容器环境中,如果内核启用了 memory cgroup,VmRSS 可能低估实际内存(共享内存和 cgroup 统计的差异导致),这是内核层面的已知限制,不是代码问题。
获取当前 JVM 自身的 PID,可以直接用:
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];这是 JVM 启动时写入的 pid@hostname 格式,在 Linux/macOS 上可用。
方案二:执行 ps 命令(跨 Unix-like 通用)
ps -o rss= -p <pid> 在 Linux 和 macOS 上都可用,输出 RSS(Linux 单位为 KB,macOS 单位为 KB),= 表示去掉表头,便于解析。
import java.io.BufferedReader;
import java.io.InputStreamReader;
public static long getRssFromPs(long pid) throws Exception {
String[] cmd = {"/bin/sh", "-c", "ps -o rss= -p " + pid};
Process process = Runtime.getRuntime().exec(cmd);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line = reader.readLine();
if (line == null || line.trim().isEmpty()) return -1L;
return Long.parseLong(line.trim()); // 单位:KB
}
}如果需要在一次调用中获取更多字段(VSZ、CPU 等),可以用 ps -o pid,rss,vsz,comm -p <pid>,然后按空白分割解析。
关键提醒:解析 ps 输出时不要假设字段位置固定。不同 Unix 系统的列顺序可能略有差异,且进程名中可能包含空格,用 split("\\s+") 后对目标字段做 trim() 和长度校验,比直接取固定下标更稳。
方案三:JNA 调用系统 API(Windows / 跨平台底层方案)
如果需要在 Windows 上获取 RSS,或者希望在 Linux/macOS 上绕过外部命令直接调用系统接口,JNA 是可行路径。
Windows:GetProcessMemoryInfo
Windows 的“物理内存”对应 PROCESS_MEMORY_COUNTERS.WorkingSetSize(工作集),语义上最接近 RSS。通过 JNA 调用 psapi.dll 的 GetProcessMemoryInfo:
import com.sun.jna.*;
import com.sun.jna.ptr.IntByReference;
public interface Psapi extends Library {
Psapi INSTANCE = Native.load("psapi", Psapi.class);
class PROCESS_MEMORY_COUNTERS extends Structure {
public int cb;
public int PageFaultCount;
public long PeakWorkingSetSize;
public long WorkingSetSize; // 物理内存工作集,最接近 RSS
// ... 其余字段
@Override
protected List<String> getFieldOrder() {
return List.of("cb", "PageFaultCount", "PeakWorkingSetSize",
"WorkingSetSize", "QuotaPeakPagedPoolUsage",
"QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage",
"QuotaNonPagedPoolUsage", "PagefileUsage", "PeakPagefileUsage");
}
}
boolean GetProcessMemoryInfo(Pointer hProcess,
PROCESS_MEMORY_COUNTERS counters, int size);
}调用前需要用 OpenProcess 打开目标进程的句柄,并申请 PROCESS_QUERY_INFORMATION | PROCESS_VM_READ 权限。WorkingSetSize 返回的是字节数,与 Linux 的 KB 单位不同,需要单独处理。
Linux/macOS:getrusage
JNA 也可以直接调用 C 库的 getrusage,读取 ru_maxrss(峰值 RSS,不是当前 RSS):
public interface CLibrary extends Library {
CLibrary INSTANCE = Native.load("c", CLibrary.class);
class Rusage extends Structure {
public long ru_maxrss; // Linux: KB; macOS: bytes
// ... 其他字段
@Override
protected List<String> getFieldOrder() {
return List.of("ru_maxrss", /* ... */);
}
}
int getrusage(int who, Rusage usage);
}getrusage 的一个关键限制是:它只返回峰值 RSS,不返回当前 RSS。ru_maxrss 在 Linux 上单位是 KB,在 macOS 上是字节,跨平台使用时必须按操作系统区分换算。如果只需要当前 RSS,Linux 下 /proc 方案更直接;如果关心的是“历史峰值内存”,getrusage 是合适的。
跨平台封装:按操作系统分派
把上述方案封装成一个统一入口,根据 os.name 系统属性分派:
public class ProcessMemory {
public static long getRssKb(long pid) throws Exception {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("linux")) {
return getRssFromProc(pid); // /proc/[pid]/status
} else if (os.contains("mac")) {
return getRssFromPs(pid); // ps -o rss=
} else if (os.contains("windows")) {
return getRssFromWindows(pid) / 1024; // JNA,字节转 KB
}
throw new UnsupportedOperationException("Unsupported OS: " + os);
}
}到此这篇关于基于Java实现查看进程内存占用情况的文章就介绍到这了,更多相关Java查看进程内存占用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
