【问题标题】:Throttling CPU/Memory usage of a Thread in Java?在 Java 中限制线程的 CPU/内存使用?
【发布时间】:2010-11-15 04:32:26
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 你使用什么线程模型? Java 任务执行器?
  • 另外,您在这个应用程序中的瓶颈在哪里?数据库? IO?
  • 我唯一能想到你想要限制 CPU 的地方是电池寿命是否是一个问题(然后你的问题就变成了,当我在做计算昂贵的事情时,我如何发现电池受限的设备?)。否则,为什么要让用户等待比他们需要的时间更长呢?如果您想保持系统响应能力,请使用低线程优先级,而不是尝试限制 CPU 使用率。
  • @nemo 在这种情况下,瓶颈只是你可以塞进运行应用程序的服务器的内存量和 CPU 能力。正如我上面提到的,陷入无限循环的线程将占用大量资源,而其他线程则任其摆布。真的很难判断某些东西是否处于无限循环中,或者它是否合法地占用大量处理器。无论哪种方式,我都不希望一两个线程占用所有资源。我希望它尽可能并行化,以便较小的不太密集的线程可以快速完成。
  • @nemo 它正在使用 Thread 对象。构造函数接受在运行时加载的类,并使用反射在该类上执行 main 方法。

标签: java memory multithreading cpu throttling


【解决方案1】:

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

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();
    }
}

【讨论】:

  • 我想让人们更容易快速查看;你介意添加更多关于你在做什么的细节,并评论代码吗?
  • 如果没有 cmets,示例代码已经太长了,这无济于事。但我会做的。
  • 这是处理内存管理的一个非常聪明的方法,也是迄今为止任何人给出的唯一好的方向。因为它对我不起作用,因为我的 OP 中的 EDIT 2,但我最终可能会使用 Aspects 来拦截为该线程创建的新对象,并使用每个线程的信号量来跟踪它。跨度>
  • System.nanoTime() 将为您提供一个纳秒时间戳,适用于基准测试和其他高分辨率时间事件。不过这不是实时的,因此您需要从前一帧/睡眠中捕获参考时间戳。
  • 如果你有一个空闲线程并且需要垃圾收集(由于某些限制阻止你跟踪对象的必要生命周期),你可以使用ReferenceQueueSoftReference s 以防止内存锁定泄漏。目前,如果一个对象是孤立的并且 GCd,那么您将无法回收该内存。如果使用你有一个线程检查引用队列,那么你可以释放信号量并记录错误。
【解决方案2】:

照顾Java Forums。基本上安排你的执行时间,然后在你花费太多时间时等待。正如在原始线程中提到的,在单独的线程中运行它并中断工作线程将得到更准确的结果,随着时间的推移取平均值也是如此。

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
}

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

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

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

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

      【讨论】:

      • 在 OP 中添加了这个原因。
      • 无限循环只会消耗一个核心。许多新服务器有 4 到 16 个内核,因此这可能不像以前那样成为问题。注意:一个独立的进程可以因任何原因被安全地杀死。
      【解决方案5】:

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

      看看这个answer 看看是否有帮助。

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

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

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

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

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

      【讨论】:

      • 在实践中这不会有帮助,因为在 Java 下,所有线程通常都将以相同的优先级执行。所以优先级通常被忽略。
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

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

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

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

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

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

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

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

            【讨论】:

              猜你喜欢
              • 2017-11-20
              • 2011-06-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-12-01
              • 1970-01-01
              • 2015-06-24
              • 1970-01-01
              相关资源
              最近更新 更多