JAVA CPU控制程序【Linux版】

背景:

资源紧张的大环境下,懂的都懂。实现这个目标,我们不需要任何第三方库,使用JDK原生的 Runtime 类即可获取CPU核心数,并利用数学计算控制线程的"忙碌"与"休眠"的比例,从而达到精确控制CPU使用率的目的,控制的是整体利用率,不是这个程序的,这点很重要

思路:

开发一个纯 Java无需第三方 Jar 包 、在 Linux 环境下运行的程序,能够将服务器的总 CPU 使用率精准控制在指定值(如 80%),并持续运行指定时间。

  1. 获取核心数 :使用 Runtime.getRuntime().availableProcessors()
  2. 创建线程:根据核心数创建对应数量的线程,确保每个核心都有一个线程在工作。
  3. 计算占比:为了达到 80% 的使用率,我们需要让线程在一个总周期内,80% 的时间在执行计算(空转),20% 的时间在休眠。
  4. 执行控制
    • 记录开始时间。
    • 执行高强度的空运算。
    • 如果运行时间超过了设定的"工作时间"(例如 80ms),则休眠剩余的时间(例如 20ms)。
  5. 打印输出信息:

操作:

直接上源代码,无侵入,使用的都是JDK自带的工具类,不会引入漏洞,另仅消耗CPU,内存几乎不耗。

CpuLoadSimulator.java

bash 复制代码
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class CpuLoadSimulator {

    private static volatile boolean isRunning = false; 
    
    private static double TARGET_CPU_LOAD = 80.0;
    private static int RUN_TIME_SECONDS = 60;
    
    // --- 优化参数 ---
    private static final long BIG_STEP_MS = 5;   // 大调整步长 (误差大时用)
    private static final long SMALL_STEP_MS = 1; // 小调整步长 (微调时用)
    private static final double DEAD_ZONE = 2.0; // 死区范围 (+/- 2.0% 内不调整)

    public static void main(String[] args) throws InterruptedException {
        // 1. 参数解析
        if (args.length > 0) {
            try { TARGET_CPU_LOAD = Double.parseDouble(args[0]); if(TARGET_CPU_LOAD<1) TARGET_CPU_LOAD=1; if(TARGET_CPU_LOAD>100) TARGET_CPU_LOAD=100; } catch (Exception e) {}
        }
        if (args.length > 1) {
            try { RUN_TIME_SECONDS = Integer.parseInt(args[1]); if(RUN_TIME_SECONDS<1) RUN_TIME_SECONDS=1; } catch (Exception e) {}
        }

        // 2. 初始化
        int coreCount = Runtime.getRuntime().availableProcessors();
        System.out.println("CPU 核心数: " + coreCount);
        System.out.println("目标负载: " + TARGET_CPU_LOAD + "%");
        System.out.println("运行时间: " + RUN_TIME_SECONDS + " 秒");
        
        String osName = System.getProperty("os.name").toLowerCase();
        if (!osName.contains("linux")) {
            System.err.println("错误:仅支持 Linux 系统。");
            return;
        }

        // 3. 启动工作线程
        List<Thread> threads = new ArrayList<>();
        final SharedSleepTime sharedSleepTime = new SharedSleepTime(100L); // 初始 100ms 休眠

        for (int i = 0; i < coreCount; i++) {
            Thread worker = new Thread(() -> {
                while (!isRunning) { Thread.yield(); }
                while (isRunning) {
                    long startTime = System.currentTimeMillis();
                    long currentSleep = sharedSleepTime.get();
                    long workTarget = 100 - currentSleep;
                    
                    while (isRunning && (System.currentTimeMillis() - startTime) < workTarget) { }
                    
                    if (isRunning && currentSleep > 0) {
                        try { Thread.sleep(currentSleep); } catch (Exception e) { break; }
                    }
                }
            });
            worker.setName("CpuWorker-" + i);
            threads.add(worker);
            worker.start();
        }

        Thread.sleep(500);
        isRunning = true;
        System.out.println("线程已就绪,开始软启动...\n");

        // 4. 主控循环
        int remainingSeconds = RUN_TIME_SECONDS;
        boolean isWarmup = true; 
        int warmupCountdown = 5; // 预热5秒
        
        // --- 新增:时间格式化 ---
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

        while (remainingSeconds > 0) {
            double systemIdle = getSystemIdleFromTop();
            double currentLoad = (systemIdle >= 0) ? (100.0 - systemIdle) : 100.0;

            long currentSleep = sharedSleepTime.get();
            long newSleep = currentSleep;
            String status = "";

            if (isWarmup) {
                // 软启动逻辑
                status = "软启动";
                warmupCountdown--;
                
                long targetSleep = (long) (100 - TARGET_CPU_LOAD);
                
                if (currentSleep > targetSleep) {
                    newSleep = currentSleep - BIG_STEP_MS;
                } else {
                    newSleep = targetSleep;
                }
                
                if (warmupCountdown <= 0 && systemIdle >= 0) {
                    isWarmup = false; // 预热结束
                }
                
            } else {
                // 稳态调节逻辑
                status = "稳调节";
                
                double error = currentLoad - TARGET_CPU_LOAD;
                
                if (Math.abs(error) <= DEAD_ZONE) {
                    newSleep = currentSleep;
                } else if (error > 0) {
                    newSleep = currentSleep + (error > 5.0 ? BIG_STEP_MS : SMALL_STEP_MS);
                } else {
                    newSleep = currentSleep - (error < -5.0 ? BIG_STEP_MS : SMALL_STEP_MS);
                }
                
                if (newSleep > 99) newSleep = 99;
                if (newSleep < 0) newSleep = 0;
            }

            sharedSleepTime.set(newSleep);

            // --- 修改点:打印当前时间 ---
            String currentTime = sdf.format(new Date());
            System.out.printf("%s [%s] 剩余: %3ds | 当前CPU: %6.2f%% | 目标: %5.1f%% | 休眠: %2dms%n", 
                    currentTime, status, remainingSeconds, currentLoad, TARGET_CPU_LOAD, newSleep);

            Thread.sleep(1000);
            remainingSeconds--;
        }

        System.out.println("\n时间到!停止...");
        isRunning = false;
        for (Thread t : threads) t.join();
        System.out.println("程序已退出。");
    }

    static class SharedSleepTime {
        private volatile long ms;
        public SharedSleepTime(long ms) { this.ms = ms; }
        public long get() { return ms; }
        public void set(long ms) { this.ms = ms; }
    }

    private static double getSystemIdleFromTop() {
        try {
            Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", "top -b -n 1 | grep \"Cpu(s)\" | awk '{print $8}'"});
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line = reader.readLine();
            if (line != null && !line.isEmpty()) {
                return Double.parseDouble(line.replace("%", "").trim());
            }
            process.destroyForcibly();
        } catch (Exception e) { }
        return -1.0;
    }
}

代码解析

  1. Runtime.getRuntime().availableProcessors():

    • 这是 Java 原生方法,返回 JVM 可用的逻辑处理器数量。如果你的 CPU 支持超线程,这里返回的是线程数(例如 8 核 16 线程,这里通常返回 16)。
  2. 控制比例 (targetLoad):

    • 我们定义了一个 100ms 的循环周期。
    • 如果要 80% 的负载,代码逻辑就是:跑满 80ms -> 睡觉 20ms -> 重复
    • 动态调整我们程序的休眠时间

如果发现 CPU 总负载低于 80% \rightarrow→ 程序减少休眠(多干活),把负载顶上去。

如果发现 CPU 总负载高于 80% \rightarrow→ 程序增加休眠(少干活),把负载降下来。

这就像汽车的定速巡航:当前速慢了就踩油门,当前速快了就踩刹车。

无论服务器上有没有其他程序在跑,它都会通过动态调整自己的算力,强行把服务器的总 CPU 使用率"钉"在 80% 左右。

3.while ((System.currentTimeMillis() - startTime) < workTimeMs):

  • 这个 while 循环内部没有任何实际逻辑,这叫做"忙等待"。它会让 CPU 处于满负荷运转状态,从而产生负载。

4.Thread.sleep(sleepTimeMs):

  • 这是让 CPU 闲置的方法。通过控制休眠的长短,我们就能控制 CPU 的总体使用率。

核心机制:

  • 如何压测 :工作线程执行一个没有任何内容的空循环while 循环)。通过疯狂读取系统时间判断是否该结束循环,让 CPU 处于"满负荷空转"状态。
  • 如何控速 :主线程每秒读取一次 top 命令。
    • 如果负载低于目标:减少线程的休眠时间(多干活)。
    • 如果负载高于目标:增加线程的休眠时间(少干活)。

使用说明:

bash 复制代码
java CpuLoadSimulator 40 30

40:目标使用率

30:持续多久

进阶使用指南:

编制启动脚本,加入定时任务crontab,自动化运行,优化加了随机时间,可选择

start.sh

bash 复制代码
#!/bin/bash

sleep $((RANDOM % 600))  # 随机延迟 0-10 分钟 (秒数)

cd /data/mdm2.0/script
nohup /usr/local/jdk/jdk8u462-b08/bin/java CpuLoadSimulator 70 180 >> nohup.out 2>&1 &

每隔30分钟,执行一次,设定目标服务器的CPU利用率:70%,持续时间:3分钟。

crontab -l

*/30 * * * * /data/mdm2.0/script/start.sh &> /dev/null

效果展示:

解惑:

1. 哪块代码在消耗 CPU?

bash 复制代码
while (isRunning) {
    long startTime = System.currentTimeMillis();
    long currentSleep = sharedSleepTime.get();
    long workTarget = 100 - currentSleep;
    
    // ====== 这里是消耗 CPU 的地方! ======
    while (isRunning && (System.currentTimeMillis() - startTime) < workTarget) {
        // 空循环体
    }
    // =====================================

    // 下面是休息,不消耗 CPU
    if (isRunning && currentSleep > 0) {
        try { Thread.sleep(currentSleep); } catch (Exception e) { break; }
    }
}

2. 它是怎么消耗的?

这种消耗方式叫做 "忙等待"。原理如下:

  1. 疯狂地"询问"时间
    System.currentTimeMillis() 是一个去操作系统读取当前时间的函数。
  2. 无意义地做数学题
    程序在 while 循环里,每一毫秒都要执行几十万甚至几百万次:
    • 读取当前时间。
    • 减去开始时间。
    • 比较是否到了目标时间。
    • 跳回循环开头。
  3. 不让 CPU 休息
    因为循环体里没有任何 Thread.sleep()I/O 操作,CPU 根本没有机会把任务挂起。它必须全神贯注、一刻不停地执行这几行简单的代码。

3. 为什么不用更复杂的代码?

你可能会问:"为什么不写个死循环算圆周率 3.14159... 或者做矩阵乘法来消耗 CPU?"

  • 简单高效:比较时间戳是一个非常轻量级的操作,对内存几乎零消耗,不会触发 Java 的垃圾回收(GC)。
  • 纯 CPU 压力:我们的目的是测试 CPU 负载能力,而不是测试计算能力或内存能力。空循环能最大限度地榨干 CPU 的"指令周期",而不受内存带宽的瓶颈影响。

4. 稳态控制版- 算法优化

  • 思路 :引入了 PID 控制(简化版)软启动
  • 改进
    1. 软启动:启动时不让线程立刻满载,而是慢慢减少休眠时间,平滑爬升到目标负载,消除了启动突刺。
    2. 死区控制:设定一个范围(如目标 ±2%),在此范围内不调整,消除了震荡现象。