在 Java 中限制线程的 CPU/内存使用率?

2022-09-01 01:07:47

我正在编写一个将运行多个线程的应用程序,并希望限制这些线程的CPU/内存使用率。

C++也有类似的问题,但如果可能的话,我想尽量避免使用C++和JNI。我意识到使用更高级的语言可能无法做到这一点,但我很好奇是否有人有任何想法。

编辑:增加了赏金;我想要一些非常好的,经过深思熟虑的想法。

编辑2:我需要这种情况是在我的服务器上执行其他人的代码。基本上,它是完全任意的代码,唯一的保证是在类文件上有一个main方法。目前,在运行时加载的多个完全不同的类作为单独的线程并发执行。

它的编写方式,重构为执行的每个类创建单独的进程将是一件痛苦的事情。如果这是通过 VM 参数限制内存使用量的唯一好方法,那就这样吧。但是我想知道是否有一种方法可以用线程来做到这一点。即使作为一个单独的进程,我也希望能够以某种方式限制其CPU使用率,因为正如我前面提到的,其中几个将同时执行。我不希望一个无限循环来占用所有资源。

编辑3:近似对象大小的一种简单方法是使用java的Instructional类;具体来说,就是 getObjectSize 方法。请注意,使用此工具需要一些特殊设置。


答案 1

如果我理解你的问题,一种方法是自适应地休眠线程,类似于在Java中完成视频播放。如果您知道自己想要 50% 的核心利用率,则您的算法应休眠约 0.5 秒 - 可能分布在一秒内(例如,0.25 秒计算,0.25 秒睡眠,例如 t.c.)。这是我的视频播放器的一个例子。

long starttime = 0; // variable declared
//...
// for the first time, remember the timestamp
if (frameCount == 0) {
    starttime = System.currentTimeMillis();
}
// the next timestamp we want to wake up
starttime += (1000.0 / fps);
// Wait until the desired next time arrives using nanosecond
// accuracy timer (wait(time) isn't accurate enough on most platforms) 
LockSupport.parkNanos((long)(Math.max(0, 
    starttime - System.currentTimeMillis()) * 1000000));

此代码将根据帧/秒值休眠。

若要限制内存使用量,可以将对象创建包装到工厂方法中,并使用某种具有有限允许值的信号量作为字节来限制估计的总对象大小(您需要估计各种对象的大小以配给信号量)。

package concur;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class MemoryLimited {
    private static Semaphore semaphore = new Semaphore(1024 * 1024, true);
    // acquire method to get a size length array
    public static byte[] createArray(int size) throws InterruptedException {
        // ask the semaphore for the amount of memory
        semaphore.acquire(size);
        // if we get here we got the requested memory reserved
        return new byte[size];
    }
    public static void releaseArray(byte[] array) {
        // we don't need the memory of array, release
        semaphore.release(array.length);
    }
    // allocation size, if N > 1M then there will be mutual exclusion
    static final int N = 600000;
    // the test program
    public static void main(String[] args) {
        // create 2 threaded executor for the demonstration
        ExecutorService exec = Executors.newFixedThreadPool(2);
        // what we want to run for allocation testion
        Runnable run = new Runnable() {
            @Override
            public void run() {
                Random rnd = new Random();
                // do it 10 times to be sure we get the desired effect
                for (int i = 0; i < 10; i++) {
                    try {
                        // sleep randomly to achieve thread interleaving
                        TimeUnit.MILLISECONDS.sleep(rnd.nextInt(100) * 10);
                        // ask for N bytes of memory
                        byte[] array = createArray(N);
                        // print current memory occupation log
                        System.out.printf("%s %d: %s (%d)%n",
                            Thread.currentThread().getName(),
                            System.currentTimeMillis(), array,
                            semaphore.availablePermits());
                        // wait some more for the next thread interleaving
                        TimeUnit.MILLISECONDS.sleep(rnd.nextInt(100) * 10);
                        // release memory, no longer needed
                        releaseArray(array);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        // run first task
        exec.submit(run);
        // run second task
        exec.submit(run);
        // let the executor exit when it has finished processing the runnables
        exec.shutdown();
    }
}

答案 2

维护 Java 论坛。基本上,当你花太多时间时,先是计时执行,然后等待。如原始线程中所述,在单独的线程中运行此操作并中断工作线程将给出更准确的结果,随时间推移的平均值也是如此。

import java.lang.management.*;

ThreadMXBean TMB = ManagementFactory.getThreadMXBean();
long time = new Date().getTime() * 1000000;
long cput = 0;
double cpuperc = -1;

while(true){

if( TMB.isThreadCpuTimeSupported() ){
    if(new Date().getTime() * 1000000 - time > 1000000000){ //Reset once per second
        time = new Date().getTime() * 1000000;
        cput = TMB.getCurrentThreadCpuTime();
    }

    if(!TMB.isThreadCpuTimeEnabled()){
        TMB.setThreadCpuTimeEnabled(true);
    }

    if(new Date().getTime() * 1000000 - time != 0)
        cpuperc = (TMB.getCurrentThreadCpuTime() - cput) / (new Date().getTime() *  1000000.0 - time) * 100.0;                  
    }
//If cpu usage is greater then 50%
if(cpuperc > 50.0){
     //sleep for a little bit.
     continue;
}
//Do cpu intensive stuff
}