web-dev-qa-db-fra.com

Limitation de l'utilisation de la mémoire et du processeur d'un thread en Java?

J'écris une application qui aura plusieurs threads en cours d'exécution et je veux limiter l'utilisation de la CPU/mémoire de ces threads.

Il y a une question similaire pour C++ , mais je veux essayer d'éviter d'utiliser C++ et JNI si possible. Je réalise que cela pourrait ne pas être possible en utilisant un langage de niveau supérieur, mais je suis curieux de voir si quelqu'un a des idées.

EDIT: Ajout d'une prime; J'aimerais avoir de très bonnes idées bien pensées à ce sujet.

EDIT 2: La situation pour laquelle j'ai besoin de ceci est l'exécution du code d'autres personnes sur mon serveur. Fondamentalement, il s'agit d'un code totalement arbitraire, la seule garantie étant qu'il y aura une méthode principale dans le fichier de classe. Actuellement, plusieurs classes complètement disparates, chargées au moment de l'exécution, s'exécutent simultanément en tant que threads distincts.

Dans sa rédaction actuelle, il serait difficile au refactor de créer des processus distincts pour chaque classe exécutée. Si c’est le seul bon moyen de limiter l’utilisation de la mémoire via les arguments VM, alors qu’il en soit ainsi. Mais j'aimerais savoir s'il y a moyen de le faire avec des threads. Même en tant que processus séparé, j'aimerais pouvoir en quelque sorte limiter l'utilisation de son processeur, car comme je l'ai mentionné plus tôt, plusieurs d'entre eux seront exécutés en même temps. Je ne veux pas d'une boucle infinie pour accaparer toutes les ressources.

EDIT 3: Les méthodes Instrumentation de Java sont un moyen facile de se rapprocher de la taille d'un objet. spécifiquement, la méthode getObjectSize. Notez qu’une configuration spéciale est nécessaire pour utiliser cet outil.

39
Alex Beardsley

Si je comprends votre problème, une solution consisterait à mettre les threads en veille de manière adaptative, de la même manière que la lecture vidéo se fait en Java. Si vous savez que vous souhaitez utiliser 50% du noyau, votre algorithme devrait dormir environ 0,5 seconde - potentiellement distribué en une seconde (par exemple, un calcul de 0,25 seconde, une veille de 0,25 seconde, etc.). Voici un exemple de mon lecteur vidéo.

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

Ce code va dormir en fonction de la valeur images/seconde.

Pour limiter l'utilisation de la mémoire, vous pouvez envelopper la création de votre objet dans une méthode d'usine et utiliser une sorte de sémaphore avec un nombre limité d'autorisations d'octets pour limiter la taille totale estimée de l'objet (vous devez estimer la taille de divers objets pour rationner le sémaphore. ).

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

Care of Forums Java . Fondamentalement, chronométrez votre exécution et attendez quand vous prenez trop de temps. Comme mentionné dans le thread d'origine, son exécution dans un thread séparé et l'interruption du thread de travail donneront des résultats plus précis, tout comme la moyenne des valeurs dans le temps.

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
Andrew

Vous pouvez obtenir beaucoup d'informations sur l'utilisation du processeur et de la mémoire via JMX , mais je ne pense pas que cela permette une manipulation active.

Pour contrôler l’utilisation du processeur dans une certaine mesure, vous pouvez utiliser Thread.setPriority () .

En ce qui concerne la mémoire, la mémoire par thread n'existe pas. Le concept même de threads Java signifie mémoire partagée. Le seul moyen de contrôler l'utilisation de la mémoire consiste à utiliser des options de ligne de commande telles que -Xmx, mais il est impossible de manipuler les paramètres au moment de l'exécution.

5
Michael Borgwardt

Vous pouvez attribuer différentes priorités aux threads afin que le thread le plus pertinent soit planifié plus souvent. 

Regardez ceci répondez pour voir si cela aide.

Lorsque tous les threads en cours ont la même priorité, ils peuvent s'exécuter de la manière suivante:

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

Lorsque vous affectez une priorité différente à l’une d’elles, cela peut ressembler à:

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

C'est-à-dire que le premier thread tourne "plus souvent" que le reste. 

1
OscarRyz

Si vous exécutez les threads dans un processus séparé, vous pouvez limiter l'utilisation de la mémoire et le nombre de processeurs ou modifier la priorité de ces threads.

Cependant, tout ce que vous ferez risque d’ajouter des frais généraux et une complexité souvent contre-productifs.

À moins que vous ne puissiez expliquer pourquoi vous voudriez le faire (par exemple, vous avez une bibliothèque mal écrite dans laquelle vous ne faites pas confiance et pour laquelle vous ne pouvez pas obtenir de soutien), je suggérerais que vous n'en avez pas besoin.

La raison pour laquelle il n'est pas facile de limiter l'utilisation de la mémoire est qu'il n'y a qu'un seul segment partagé. Ainsi, un objet qui est utilisé dans un thread est utilisable dans un autre et n'est pas affecté à un thread ou à un autre.

Limiter l’utilisation du processeur signifie arrêter tous les threads pour qu’ils ne fassent rien. Cependant, une meilleure approche consiste à s’assurer que le thread ne gaspille pas le CPU et qu’il effectue uniquement un travail qui doit être effectué. vouloir les empêcher de le faire.

1
Peter Lawrey

Pourquoi pas au lieu de faire du "threading" en multitâche coopératif, il serait intéressant de voir si vous pouvez manipuler http://www.janino.net/ pour exécuter un programme pendant un certain temps/un ensemble d’instructions, puis arrêtez et exécutez le programme suivant. Au moins de cette façon, son juste, donner à tout le monde la même tranche de temps ...

1
JH.

Thread.setPriority () peut aider, mais il ne vous permet pas de limiter le processeur utilisé par un thread. En fait, je n'ai jamais entendu parler d'une bibliothèque Java qui le fasse.

Il pourrait être possible de mettre en place une telle installation à condition que vos threads soient prêts à coopérer. Il est essentiel que les threads appellent périodiquement un planificateur personnalisé et que celui-ci surveille l'utilisation du processeur par JMX. Mais le problème est que si un thread ne passe pas l'appel du planificateur assez souvent, il risque de dépasser les limites de limitation. Et vous ne pouvez rien faire contre un fil qui reste bloqué dans une boucle.

Une autre voie théorique pour la mise en œuvre serait d'utiliser des isolats. Malheureusement, vous aurez du mal à trouver une machine virtuelle Java à usage général qui implémente des isolats. En outre, les API standard vous permettent uniquement de contrôler l'isolement, pas les threads au sein de l'isolat. 

0
Stephen C

Le seul moyen de limiter l'utilisation du processeur de thread est de bloquer une ressource ou d'appeler fréquemment yield ().

Cela ne limite pas l'utilisation du processeur en dessous de 100% mais donne aux autres threads et traite plus de tranches de temps.

0