44

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

C++有一个类似的问题,但我想尽可能避免使用 C++ 和 JNI。我意识到使用更高级别的语言可能无法做到这一点,但我很想知道是否有人有任何想法。

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

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

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

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

4

9 回答 9

32

如果我理解您的问题,一种方法是自适应地使线程休眠,就像在 Java 中完成视频播放一样。如果您知道您想要 50% 的核心利用率,您的算法应该休眠大约 0.5 秒 - 可能在一秒内分布(例如 0.25 秒计算,0.25 秒休眠等)。这是我的视频播放器的一个例子

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();
    }
}
于 2009-07-30T08:49:01.747 回答
6

关心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
}
于 2009-08-05T20:19:15.437 回答
5

您可以通过JMX获得很多有关 CPU 和内存使用情况的信息,但我认为它不允许任何主动操作。

为了在一定程度上控制 CPU 使用率,您可以使用Thread.setPriority()

至于内存,没有每线程内存这样的东西。Java线程的概念意味着共享内存。控制内存使用的唯一方法是通过 -Xmx 等命令行选项,但无法在运行时操作设置。

于 2009-07-29T18:46:55.050 回答
2

如果您在单独的进程中运行线程,则可以限制内存使用量并限制 CPU 数量或更改这些线程的优先级。

但是,您所做的任何事情都可能会增加开销和复杂性,这通常会适得其反。

除非你能解释你为什么要这样做(例如你有一个写得很糟糕的库,你不信任并且无法获得支持)我建议你不需要这样做。

限制内存使用不容易的原因是只有一个堆是共享的。因此,在一个线程中使用的对象可以在另一个线程中使用,并且不会分配给一个线程或另一个线程。

限制 CPU 使用意味着停止所有线程,这样它们就不会做任何事情,但是更好的方法是确保线程不会浪费 CPU,并且只在做需要完成的工作,在这种情况下你不会想阻止他们这样做。

于 2009-08-05T20:40:03.600 回答
1

您可以为线程分配不同的优先级,以便更频繁地调度最相关的线程。

看看这个答案,看看是否有帮助。

当所有正在运行的线程具有相同的优先级时,它们可能会像这样运行:

t1, t2, t3,     t1, t2, t3,   t1, t2, t3

当您为其中一个分配不同的优先级时,它可能如下所示:

t1, t1, t1, t1,    t2,    t1, t1, t1 t3.

也就是说,第一个线程比其他线程“更频繁地”运行。

于 2009-07-29T18:54:09.963 回答
1

为什么不做“线程”而不是合作多任务,看看你是否可以操纵http://www.janino.net/来运行一个程序一段时间/一组指令,然后停止并运行会很有趣下一个节目。至少这样是公平的,给每个人相同的时间片......

于 2009-08-07T03:44:32.223 回答
0

Thread.setPriority() 可能会有所帮助,但它不允许您限制线程使用的 CPU。事实上,我还没有听说过有任何 Java 库可以做到这一点。

如果您的线程准备好进行合作,那么实现这样的工具是可能的。关键是让线程定期调用自定义调度程序,并让调度程序使用 JMX 监控线程 CPU 使用率。但问题是,如果某个线程没有足够频繁地调用调度程序,它很可能会超过限制。对于陷入循环的线程,您无能为力。

另一种实现的理论途径是使用隔离。不幸的是,您将很难找到实现隔离的通用 JVM。此外,标准 API 只允许您控制隔离,而不是隔离内的线程。

于 2009-07-30T03:12:41.170 回答
0

限制线程 CPU 使用的唯一方法是阻止资源或频繁调用 yield()。

这不会将 CPU 使用率限制在 100% 以下,但会为其他线程和进程提供更多时间片。

于 2009-07-30T08:21:28.790 回答
-1

为了减少 CPU,你希望你的线程在常见的ifwhile循环中休眠。

while(whatever) {
    //do something
    //Note the capitol 'T' here, this sleeps the current thread.
    Thread.sleep(someNumberOfMilliSeconds);
}

休眠几百毫秒将大大降低 CPU 使用率,而对性能几乎没有明显影响。

至于内存,我会在各个线程上运行分析器并进行一些性能调整。如果您限制了线程可用的内存量,我认为可能会出现内存不足异常或线程不足。我相信 JVM 能够提供线程所需的尽可能多的内存,并通过在任何给定时间仅将基本对象保留在范围内来减少内存使用量。

于 2009-08-05T19:39:17.827 回答