如何监控Java Process API启动的Linux子进程的CPU与内存占用?
监控通过ProcessBuilder启动的Linux子进程资源占用
在Linux环境下,你可以通过以下几种方式监控ProcessBuilder.start()启动的子进程的CPU、内存等资源占用:
方法一:结合系统命令与Java Process API
利用Linux自带的进程查询命令(如ps、pidstat),结合Java获取到的子进程PID来查询资源数据:
步骤:
获取子进程PID
- Java 9及以上:直接调用
Process.pid()方法获取PID。 - Java 8及以下:通过反射解析Process类的内部字段,或从
process.toString()的输出中提取(Linux下格式通常为Process[pid=xxx])。
- Java 9及以上:直接调用
执行系统命令查询资源
常用命令示例:- 查看CPU使用率与常驻内存:
ps -p <pid> -o %cpu,rss - 更详细的CPU统计:
pidstat -p <pid> 1 1 - 实时资源快照:
top -b -n 1 -p <pid>
- 查看CPU使用率与常驻内存:
定时监控多进程
使用ScheduledExecutorService定期对每个存活的子进程执行查询,避免阻塞主线程。
代码示例(Java 9+):
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class MultiProcessMonitor { public static void main(String[] args) throws IOException { // 启动多个子进程 List<Process> processes = new ArrayList<>(); processes.add(new ProcessBuilder("sleep", "120").start()); processes.add(new ProcessBuilder("dd", "if=/dev/zero", "of=/dev/null").start()); ScheduledExecutorService monitorExecutor = Executors.newScheduledThreadPool(1); // 每2秒执行一次监控 monitorExecutor.scheduleAtFixedRate(() -> { for (Process proc : processes) { if (!proc.isAlive()) { System.out.printf("进程PID %d 已退出%n", proc.pid()); continue; } long pid = proc.pid(); try { // 执行ps命令获取CPU和内存数据 Process psProc = new ProcessBuilder("ps", "-p", String.valueOf(pid), "-o", "%cpu,rss").start(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(psProc.getInputStream()))) { reader.readLine(); // 跳过表头行 String dataLine = reader.readLine(); if (dataLine != null) { String[] parts = dataLine.trim().split("\\s+"); String cpuUsage = parts[0]; String rssMemory = parts[1] + " KB"; System.out.printf("进程PID %d: CPU使用率 %s%%, 常驻内存 %s%n", pid, cpuUsage, rssMemory); } } psProc.waitFor(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }, 0, 2, TimeUnit.SECONDS); } }
方法二:直接读取Linux /proc文件系统
Linux下每个进程的资源信息都以文件形式存储在/proc/<pid>/目录下,直接读取这些文件无需执行外部命令,效率更高:
常用的proc文件:
/proc/<pid>/stat:包含进程的CPU时间、状态等信息/proc/<pid>/status:包含内存占用、线程数等可读格式的数据/proc/<pid>/statm:内存使用的统计信息(单位为页)
代码示例(读取常驻内存):
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ProcFileMultiMonitor { public static void main(String[] args) throws IOException { List<Process> processes = new ArrayList<>(); processes.add(new ProcessBuilder("sleep", "120").start()); processes.add(new ProcessBuilder("dd", "if=/dev/zero", "of=/dev/null").start()); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(() -> { for (Process proc : processes) { if (!proc.isAlive()) { System.out.printf("进程PID %d 已退出%n", proc.pid()); continue; } long pid = proc.pid(); String statusPath = "/proc/" + pid + "/status"; try { for (String line : Files.readAllLines(Paths.get(statusPath))) { if (line.startsWith("VmRSS:")) { String[] parts = line.trim().split("\\s+"); String memoryInfo = parts[1] + " " + parts[2]; System.out.printf("进程PID %d: 常驻内存 %s%n", pid, memoryInfo); break; } } } catch (IOException e) { e.printStackTrace(); } } }, 0, 2, TimeUnit.SECONDS); } }
精确计算CPU使用率
若需要精确计算CPU使用率,需两次读取/proc/<pid>/stat和/proc/stat(系统总CPU时间),通过时间差值计算:
- 读取
/proc/<pid>/stat的第14(utime)、15(stime)字段(进程用户态/内核态CPU时间,单位为jiffies) - 读取
/proc/stat第一行的所有数值之和(系统总CPU时间) - 间隔一段时间后重复读取,计算差值:
进程CPU总时间差 = (utime2 + stime2) - (utime1 + stime1) 系统总CPU时间差 = systemCpuTime2 - systemCpuTime1 CPU使用率 = (进程CPU总时间差 / 系统总CPU时间差) * 100 * 可用CPU核数
注意事项
- 子进程退出后,对应的
/proc/<pid>/目录会被删除,查询前需先调用process.isAlive()判断状态 - Java 8及以下获取PID的反射实现:
private static long getPidForJava8(Process process) throws NoSuchFieldException, IllegalAccessException { Field pidField = process.getClass().getDeclaredField("pid"); pidField.setAccessible(true); return pidField.getLong(process); } - 不同Linux发行版的proc文件格式或命令输出可能略有差异,需做好兼容处理
内容的提问来源于stack exchange,提问作者Thomas Bratt
相关产品推荐
相关产品推荐

