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

44

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

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

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

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

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

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


你正在使用什么线程模型?Java 任务执行器吗? - James McMahon
此外,你的应用程序中的瓶颈在哪里?数据库?IO? - James McMahon
我能想到唯一需要限制CPU的情况是电池寿命成问题时(那么你的问题就变成了,如何发现在电池受限设备上进行计算密集型操作?)。否则,为什么要让用户等待比必要时间更长呢?如果您想保持系统响应性,请使用低线程优先级而不是尝试限制CPU使用率。 - Dan Davies Brackett
1
@nemo 在这种情况下,瓶颈只在于你可以塞进运行应用程序的服务器的内存和CPU功率的数量。正如我上面提到的,一个陷入无限循环的线程将占用大量资源,让其他线程任其摆布。很难判断某个东西是否处于无限循环状态,还是合理地占用了处理器资源。无论哪种情况,我都不希望一个或两个线程占用所有资源。我希望这尽可能并行化,这样较小、较不密集的线程可以快速完成。 - Alex Beardsley
@nemo 这里使用了 Thread 对象。构造函数接受在运行时加载的类,并使用反射在该类上执行 main 方法。 - Alex Beardsley
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();
    }
}

我希望让人们更轻松快速地查看这个;你介意多添加一些关于你正在做什么的详细信息,并注释代码吗? - Alex Beardsley
这并没有太大帮助,因为示例代码已经太长了,没有注释。但我会做的。 - akarnokd
这是一种非常聪明的处理内存管理的方式,也是目前为止任何人给出的唯一好方向。但由于我的 OP 中的 EDIT 2,它对我来说不起作用,但我可能最终会使用 Aspects 来拦截为该线程创建的新对象,并使用每个线程的信号量来跟踪它。 - Alex Beardsley
System.nanoTime()会给你一个纳秒级别的时间戳,适用于基准测试和其他高分辨率时间事件。但它不是实时的,所以你需要从前一帧/休眠中捕获一个参考时间戳。 - davenpcj
1
如果您有一个空闲的线程并且需要进行垃圾回收(由于某些限制导致无法跟踪对象的必要生命周期),则可以使用ReferenceQueueSoftReference来防止内存锁泄漏。目前,如果一个对象被孤立并且被GCd,则无法回收该内存。如果使用一个检查引用队列的线程,那么您可以释放信号量并记录错误。 - Rob Hall

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
}

5
你可以通过 JMX 获取有关 CPU 和内存使用情况的大量信息,但我认为它不允许任何主动操作。
要在一定程度上控制 CPU 使用率,可以使用 Thread.setPriority()
至于内存,不存在每个线程的内存。Java 线程的概念意味着共享内存。唯一控制内存使用的方法是通过命令行选项如 -Xmx,但没有办法在运行时操作设置。

2
如果您在单独的进程中运行线程,则可以限制内存使用量、限制CPU数量或更改这些线程的优先级。但是,任何操作都可能增加开销和复杂性,通常是适得其反的。除非您能解释为什么要这样做(例如,您有一个编写不良且无法获得支持的库),否则建议您不需要这样做。之所以不容易限制内存使用量是因为只有一个堆是共享的。因此,在一个线程中使用的对象也可用于另一个线程,并且未分配给一个特定的线程。限制CPU使用率意味着停止所有线程,使它们不执行任何操作。然而,更好的方法是确保线程不浪费CPU,只有在需要完成工作时才活动,这种情况下您不会想让它们停止。

1
在原帖中添加了这个原因的解释。 - Alex Beardsley
一个无限循环将只会占用一个核心。现在许多新的服务器拥有4到16个核心,所以这可能不再是过去那么大的问题了。注意:独立进程可以因任何原因被安全地杀死。 - Peter Lawrey

1

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

查看答案,看看是否有帮助。

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

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

当您为其中一个分配不同的优先级时,它可能会看起来像:

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

也就是说,第一个线程运行的频率比其他线程更高。


实际上这并没有帮助,因为在Java下通常所有的线程都将以相同的优先级执行。因此,优先级通常会被忽略。 - Thomas Hunziker

1

为什么不使用协作多任务处理,而是使用“线程”呢?如果你可以操纵http://www.janino.net/使其在一定时间/指令集内运行程序,然后停止并运行下一个程序,这将非常有趣。至少这样公平,给每个人相同的时间片...


0

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

如果你的线程准备好合作,可能可以实现这样的功能。关键是让线程定期调用自定义调度程序,并使用JMX监视线程CPU使用情况。但问题在于,如果某个线程不经常调用调度程序,它很可能会超过限制。而且对于陷入循环的线程,你无能为力。

另一种理论上实现的方法是使用Isolates。不幸的是,你很难找到一个实现隔离的通用JVM。此外,标准API只允许你控制隔离,而不能控制隔离中的线程。


0

你可以通过阻塞资源或频繁调用yield()来限制线程的CPU使用率。

这并不能将CPU使用率限制在100%以下,但可以给其他线程和进程更多的时间片。


-1
为了减少 CPU 的使用,您可以在常见的 if 和 while 循环中让线程休眠。
while(whatever) {
    //do something
    //Note the capitol 'T' here, this sleeps the current thread.
    Thread.sleep(someNumberOfMilliSeconds);
}

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接