Дросселирование использования ЦП/памяти потока в Java?

Я пишу приложение, в котором будет работать несколько потоков, и хочу ограничить использование ЦП/памяти этих потоков.

Существует похожий вопрос для C++, но я хочу попытаться избежать использования C++ и JNI, если возможный. Я понимаю, что это может быть невозможно с использованием языка более высокого уровня, но мне любопытно узнать, есть ли у кого-нибудь какие-либо идеи.

EDIT: добавлена ​​награда; Я хотел бы некоторые действительно хорошие, хорошо продуманные идеи по этому поводу.

РЕДАКТИРОВАТЬ 2: Ситуация, в которой мне это нужно, - это выполнение чужого кода на моем сервере. По сути, это совершенно произвольный код с единственной гарантией того, что в файле класса будет основной метод. В настоящее время несколько совершенно разных классов, которые загружаются во время выполнения, выполняются одновременно как отдельные потоки.

В том виде, как это написано, было бы сложно провести рефакторинг, чтобы создать отдельные процессы для каждого выполняемого класса. Если это единственный хороший способ ограничить использование памяти с помощью аргументов виртуальной машины, пусть будет так. Но я хотел бы знать, есть ли способ сделать это с помощью потоков. Даже в качестве отдельного процесса я хотел бы каким-то образом ограничить его использование ЦП, поскольку, как я упоминал ранее, несколько из них будут выполняться одновременно. Я не хочу, чтобы бесконечный цикл поглощал все ресурсы.

РЕДАКТИРОВАТЬ 3: простой способ приблизить размер объекта — использовать Инструментальные классы; в частности, метод getObjectSize. Обратите внимание, что для использования этого инструмента необходима специальная настройка.


person Alex Beardsley    schedule 29.07.2009    source источник
comment
Что вы используете для потоковой модели? Исполнители задач Java?   -  person James McMahon    schedule 05.08.2009
comment
Кроме того, где ваши узкие места в этом приложении? База данных? ИО?   -  person James McMahon    schedule 05.08.2009
comment
Единственный раз, когда я могу придумать, где вы хотели бы ограничить ЦП, было, если время автономной работы было проблемой (и тогда ваш вопрос звучит так: как я узнаю, когда я делаю вычислительно ресурсоемкие вещи на устройстве с ограниченным аккумулятором?). Иначе зачем заставлять пользователя ждать дольше, чем нужно? Если вы хотите сохранить скорость отклика системы, используйте низкий приоритет потока, а не пытайтесь ограничить использование ЦП.   -  person Dan Davies Brackett    schedule 06.08.2009
comment
@nemo Узким местом в этом случае является только объем памяти и мощность ЦП, которые вы можете втиснуть в сервер, на котором запущено приложение. Как я уже упоминал выше, поток, застрявший в бесконечном цикле, будет потреблять тонны ресурсов, оставляя другие потоки на его милость. Было бы очень сложно сказать, находится ли что-то в бесконечном цикле или это законно интенсивно использует процессор. В любом случае я не хочу, чтобы один или два потока поглощали все ресурсы. Я бы хотел, чтобы это было максимально распараллелено, чтобы меньшие менее интенсивные потоки могли быстро заканчиваться.   -  person Alex Beardsley    schedule 06.08.2009
comment
@nemo использует объекты Thread. Конструктор принимает класс, загружаемый во время выполнения, и использует отражение для выполнения основного метода этого класса.   -  person Alex Beardsley    schedule 06.08.2009


Ответы (9)


Если я понимаю вашу проблему, одним из способов было бы адаптивное засыпание потоков, аналогично воспроизведению видео в 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();
    }
}
person akarnokd    schedule 30.07.2009
comment
Я бы хотел, чтобы людям было проще на это быстро взглянуть; не могли бы вы добавить немного больше подробностей о том, что вы делаете, и прокомментировать код? - person Alex Beardsley; 05.08.2009
comment
Это мало поможет, так как пример кода уже слишком длинный без комментариев. Но я сделаю это. - person akarnokd; 05.08.2009
comment
Это действительно умный способ управления памятью, и это единственное правильное направление, которое кто-либо дал до сих пор. Как бы то ни было, это не сработает для меня из-за EDIT 2 в моем OP, но я, вероятно, в конечном итоге буду использовать Aspects для перехвата новых объектов, создаваемых для этого потока, и отслеживать его с помощью семафора для каждого потока. - person Alex Beardsley; 06.08.2009
comment
System.nanoTime() даст вам отметку времени в наносекундах, удобную для бенчмаркинга и других событий времени с высоким разрешением. Однако это не реальное время, поэтому вам нужно будет захватить эталонную временную метку из предыдущего кадра/сна. - person davenpcj; 10.02.2013
comment
Если у вас есть запасной поток и вам требуется сборка мусора (из-за некоторых ограничений, не позволяющих вам отслеживать необходимое время жизни ваших объектов), вы потенциально можете использовать ReferenceQueue и SoftReference для предотвращения утечки памяти. В настоящее время, если объект потерян и GCd, вы не можете восстановить эту память. Если у вас есть поток, проверяющий очередь ссылок, вы можете освободить семафор и зарегистрировать ошибку. - person Rob Hall; 31.10.2013

Уход за форумами 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
}
person Andrew    schedule 05.08.2009

Вы можете получить много информации об использовании ЦП и памяти через JMX, но я не думаю, что он допускает какие-либо активные манипуляции.

Для некоторого контроля использования ЦП вы можете использовать Thread.setPriority().

Что касается памяти, то не существует такой вещи, как память на поток. Сама концепция потоков Java означает разделяемую память. Единственный способ контролировать использование памяти — использовать параметры командной строки, такие как -Xmx, но нет возможности манипулировать настройками во время выполнения.

person Michael Borgwardt    schedule 29.07.2009

Если вы запускаете потоки в отдельном процессе, вы можете ограничить использование памяти и ограничить количество процессоров или изменить приоритет этих потоков.

Однако все, что вы делаете, скорее всего, приведет к увеличению накладных расходов и сложности, что часто приводит к обратным результатам.

Если вы не можете объяснить, почему вы хотите это сделать (например, у вас есть плохо написанная библиотека, которой вы не доверяете и для которой не можете получить поддержку), я бы посоветовал вам не делать этого.

Причина, по которой нелегко ограничить использование памяти, заключается в том, что существует только одна общая куча. Таким образом, объект, который используется в одном потоке, может использоваться в другом и не назначается тому или иному потоку.

Ограничение использования ЦП означает остановку всех потоков, чтобы они ничего не делали, однако лучший подход состоит в том, чтобы убедиться, что поток не тратит впустую ЦП и активно выполняет только ту работу, которую необходимо выполнить, и в этом случае вы бы не хочу остановить их делать это.

person Peter Lawrey    schedule 05.08.2009
comment
Добавлено обоснование этого в ОП. - person Alex Beardsley; 06.08.2009
comment
Бесконечный цикл потребляет только одно ядро. Многие новые серверы имеют от 4 до 16 ядер, поэтому это может быть не такой проблемой, как раньше. Примечание: независимый процесс можно безопасно убить по любой причине. - person Peter Lawrey; 08.08.2009

Вы можете назначать разные приоритеты потокам, чтобы наиболее релевантные потоки планировались чаще.

Посмотрите на этот ответ, чтобы узнать, поможет ли это.

Когда все запущенные потоки имеют одинаковый приоритет, они могут работать следующим образом:

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

Когда вы назначаете другой приоритет одному из них, это может выглядеть так:

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

То есть первый поток выполняется «чаще», чем остальные.

person OscarRyz    schedule 29.07.2009
comment
На практике это не поможет, потому что в Java обычно все потоки выполняются с одинаковым приоритетом. Таким образом, приоритет обычно игнорируется. - person Thomas Hunziker; 04.09.2017

Почему бы вместо «потоков» не использовать совместную многозадачность, было бы интересно посмотреть, сможете ли вы манипулировать http://www.janino.net/ для запуска программы в течение определенного времени/набора инструкций, затем остановите и запустите следующую программу. По крайней мере, так будет справедливо, дайте всем одинаковый кусок времени...

person JH.    schedule 07.08.2009

Thread.setPriority() может помочь, но не позволяет ограничивать ЦП, используемый потоком. На самом деле, я не слышал ни о какой библиотеке Java, которая делает это.

Возможно реализовать такую ​​возможность при условии, что ваши потоки готовы к взаимодействию. Суть в том, чтобы потоки периодически обращались к пользовательскому планировщику, а планировщик контролировал использование ЦП потоком с помощью JMX. Но проблема в том, что если какой-то поток недостаточно часто вызывает планировщик, он вполне может превысить ограничения регулирования. И вы ничего не можете сделать с потоком, который застревает в цикле.

Другим теоретическим путем реализации было бы использование изолятов. К сожалению, вам будет трудно найти JVM общего назначения, реализующую изоляты. Кроме того, стандартные API позволяют управлять только изолятом, а не потоками внутри изолята.

person Stephen C    schedule 30.07.2009

Единственный способ ограничить использование ЦП потока — либо заблокировать ресурс, либо часто вызывать yield().

Это не ограничивает использование ЦП ниже 100%, но дает другим потокам и процессам больше временных интервалов.

person Thorbjørn Ravn Andersen    schedule 30.07.2009

Чтобы снизить нагрузку на ЦП, вы хотите засыпать свои потоки внутри обычных циклов if и while.

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

Спящий режим в течение нескольких сотен миллисекунд значительно снизит загрузку процессора практически без заметного влияния на производительность.

Что касается памяти, я бы запустил профилировщик для отдельных потоков и немного настроил производительность. Если вы перекрыли объем памяти, доступной для потока, я думаю, что скорее всего возникнет исключение нехватки памяти или голодный поток. Я бы доверил JVM предоставить столько памяти, сколько нужно потоку, и работать над сокращением использования памяти, оставляя в области видимости только важные объекты в любой момент времени.

person James McMahon    schedule 05.08.2009