web-dev-qa-db-fra.com

La méthode JavaScript setInterval () provoque-t-elle une fuite de mémoire?

Développement d'un projet d'animation basé sur JavaScript.

J'ai remarqué que l'utilisation appropriée de setInterval(), setTimeout() et même requestAnimationFrame alloue de la mémoire sans ma demande et provoque des appels fréquents de récupération de place. Plus d'appels GC = scintille :

Par exemple; lorsque j'exécute ce qui suit --- (code simple en appelant init () dans Google Chrome, l'allocation de mémoire + la récupération de place est correcte pendant les 20-30 premières secondes ...

function init()
{
    var ref = window.setInterval(function() { draw(); }, 50);
}

function draw()
{
    return true
}

D'une manière ou d'une autre, dans une minute environ, commence une étrange augmentation de la mémoire allouée! Puisque init () n'est appelé qu'une seule fois, quelle est la raison de l'augmentation de la taille de la mémoire allouée?

(Modifier: chrome capture d'écran téléchargée)

chrome screenshot

REMARQUE # 1: Oui, j'ai essayé d'appeler clearInterval () avant le prochain setInterval (). Le problème reste le même!

REMARQUE # 2: Afin d'isoler le problème, je garde le code ci-dessus simple et stupide.

61
matahari

EDIT: réponse de Yury est mieux.


tl; dr IMO il n'y a pas de fuite de mémoire. La pente positive est simplement l'effet de setInterval et setTimeout. Les ordures sont collectées, comme le montrent les motifs en dents de scie, ce qui signifie par définition qu'il n'y a pas de fuite de mémoire. (Je pense).

Je ne suis pas sûr qu'il existe un moyen de contourner cette soi-disant "fuite de mémoire". Dans ce cas, "fuite de mémoire" fait référence à chaque appel à la fonction setInterval augmentant l'utilisation de la mémoire, comme le montrent les pentes positives dans le profileur de mémoire.

La réalité est qu'il n'y a pas de fuite de mémoire réelle: le garbage collector est toujours en mesure de collecter la mémoire. La fuite de mémoire par définition "se produit lorsqu'un programme informatique acquiert de la mémoire mais ne parvient pas à la restituer au système d'exploitation".

Comme le montrent les profils de mémoire ci-dessous, aucune fuite de mémoire ne se produit. L'utilisation de la mémoire augmente à chaque appel de fonction. L'OP s'attend à ce que, car il s'agit de la même fonction appelée à plusieurs reprises, il ne devrait pas y avoir d'augmentation de mémoire. Cependant, ce n'est pas le cas. La mémoire est consommée à chaque appel de fonction. Finalement, les déchets sont collectés, créant le motif en dents de scie.

J'ai exploré plusieurs façons de réorganiser les intervalles, et ils conduisent tous au même motif en dents de scie (bien que certaines tentatives conduisent à un ramassage des ordures qui ne se produit jamais car les références ont été conservées).

function doIt() {
    console.log("hai")
}

function a() {
    doIt();
    setTimeout(b, 50);
}
function b() {
    doIt();
    setTimeout(a, 50);
}

a();

http://fiddle.jshell.net/QNRSK/14/

function b() {
    var a = setInterval(function() {
        console.log("Hello");
        clearInterval(a);
        b();                
    }, 50);
}
b();

http://fiddle.jshell.net/QNRSK/17/

function init()
{
    var ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
    console.log('Hello');
}
init();

http://fiddle.jshell.net/QNRSK/20/

function init()
{
    window.ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
    console.log('Hello');
    clearInterval(window.ref);
    init();
}
init();​

http://fiddle.jshell.net/QNRSK/21/

Apparemment, setTimeout et setInterval ne font pas officiellement partie de Javascript (donc ils ne font pas partie de la v8). L'implémentation est laissée à l'implémenteur. Je vous suggère de jeter un œil à l'implémentation de setInterval et autres dans node.js

51
Luqmaan

Le problème ici n'est pas dans le code lui-même, il ne fuit pas. C'est à cause de la façon dont le panneau Timeline est implémenté. Lorsque Timeline enregistre des événements, nous collectons des traces de pile JavaScript à chaque appel de rappel setInterval. La trace de pile est d'abord allouée dans le tas JS, puis copiée dans des structures de données natives. Une fois la trace de pile copiée dans l'événement natif, elle devient une ordure dans le tas JS. Cela se reflète sur le graphique. La désactivation de l'appel suivant http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/TimelineRecordFactory.cpp#L55 rend le graphique de la mémoire plat.

Il existe un bogue lié à ce problème: https://code.google.com/p/chromium/issues/detail?id=120186

28
Yury Semikhatsky

Chaque fois que vous effectuez un appel de fonction, il crée un frame de pile . Contrairement à beaucoup d'autres langages, Javascript stocke le cadre de pile sur le tas, comme tout le reste. Cela signifie que chaque fois que vous appelez une fonction, ce que vous faites toutes les 50 ms, un nouveau cadre de pile est ajouté au tas. Cela s'additionne et est finalement récupéré.

C'est un peu inévitable, étant donné le fonctionnement de Javascript. La seule chose qui peut vraiment être faite pour l'atténuer est de rendre les cadres de pile aussi petits que possible, ce que je suis sûr que toutes les implémentations font.

12
ICR

Je voulais répondre à votre commentaire sur setInterval et le scintillement:

J'ai remarqué que l'utilisation correcte de setInterval (), setTimeout () et même requestAnimationFrame alloue de la mémoire sans ma demande et provoque des appels fréquents de récupération de place. Plus d'appels GC = scintille :

Vous voudrez peut-être essayer de remplacer l'appel setInterval par une fonction auto-invoquante less evil basée sur setTimeout. Paul Irish le mentionne dans le discours intitulé 10 choses que j'ai apprises de la source jQuery (vidéo --- (ici , notes ici voir # 2). Ce que vous faites est de remplacer votre appel à setInterval par une fonction qui s'invoque indirectement via setTimeout après avoir terminé le travail qu'il est censé faire . Pour citer le discours:

Beaucoup ont soutenu que setInterval est une mauvaise fonction. Il continue d'appeler une fonction à des intervalles spécifiés, que la fonction soit terminée ou non.

En utilisant votre exemple de code ci-dessus, vous pouvez mettre à jour votre fonction init à partir de:

function init() 
{
    var ref = window.setInterval(function() { draw(); }, 50);
}

à:

function init()
{
     //init stuff

     //awesome code

     //start rendering
     drawLoop();
}

function drawLoop()
{
   //do work
   draw();

   //queue more work
   setTimeout(drawLoop, 50);
}

Cela devrait aider un peu car:

  1. draw () ne sera plus appelé par votre boucle de rendu tant qu'il ne sera pas terminé
  2. comme le soulignent la plupart des réponses ci-dessus, tous les appels de fonction ininterrompus de setInterval mettent du temps sur le navigateur.
  3. le débogage est un peu plus facile car vous n'êtes pas interrompu par le tir continu de setInterval

J'espère que cela t'aides!

6
mrdc

Essayez de le faire sans la fonction anonyme. Par exemple:

function draw()
{
    return true;
}

function init()
{
    var ref = window.setInterval(draw, 50);
}

Se comporte-t-il toujours de la même manière?

3
antimeme

Chrome ne voit pratiquement pas de pression de mémoire de votre programme (1,23 Mo est une utilisation de mémoire très faible par rapport aux normes d'aujourd'hui), donc il ne pense probablement pas qu'il ait besoin de GC de manière agressive. Si vous modifiez votre programme pour utiliser plus de mémoire, vous verrez le garbage collector se déclencher. essaye ça:

<!html>
<html>
<head>
<title>Where goes memory?</title>
</head>
<body>

Greetings!

<script>
function init()
{
    var ref = window.setInterval(function() { draw(); }, 50);
}

function draw()
{
    var ar = new Array();
    for (var i = 0; i < 1e6; ++i) {
        ar.Push(Math.Rand());
    }
    return true
}

init();
</script>

</body>
</html>

Lorsque je lance cela, j'obtiens un modèle d'utilisation de la mémoire des dents de scie, atteignant un pic autour de 13,5 Mo (encore une fois, assez petit par rapport aux normes d'aujourd'hui).

PS: Spécificités de mes navigateurs:

Google Chrome   23.0.1271.101 (Official Build 172594)
OS  Mac OS X
WebKit  537.11 (@136278)
JavaScript  V8 3.13.7.5
Flash   11.5.31.5
User Agent  Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
3
allyourcode

Il ne semble pas y avoir de fuite de mémoire. Tant que l'utilisation de la mémoire diminue à nouveau après le GC et que l'utilisation globale de la mémoire n'a pas tendance à augmenter en moyenne, il n'y a pas de fuite.

La "vraie" question que je vois ici est que setInterval utilise effectivement de la mémoire pour fonctionner, et il ne semble pas qu'il devrait allouer quoi que ce soit. En réalité, il doit allouer quelques éléments:

  1. Il devra allouer de l'espace de pile pour exécuter à la fois la fonction anonyme et la routine draw ().
  2. Je ne sais pas s'il faut allouer des données temporaires pour effectuer les appels eux-mêmes (probablement pas)
  3. Il doit allouer une petite quantité de stockage pour conserver cette true valeur de retour de draw().
  4. En interne, setInterval peut allouer de la mémoire supplémentaire pour reprogrammer un événement récurrent (je ne sais pas comment cela fonctionne en interne, il peut réutiliser l'enregistrement existant).
  5. Le JIT peut essayer de tracer cette méthode, ce qui allouerait du stockage supplémentaire pour la trace et certaines métriques. Le VM peut déterminer que cette méthode est trop petite pour la tracer, je ne sais pas exactement quels sont tous les seuils pour activer ou désactiver le traçage. Si vous exécutez ce code assez longtemps pour le = VM pour l'identifier comme "chaud", il peut allouer encore plus de mémoire pour contenir le code machine compilé JIT (après quoi, je m'attends à ce que l'utilisation moyenne de la mémoire diminue, car le code machine généré devrait allouer moins de mémoire dans la plupart des cas)

Chaque fois que votre fonction anonyme s'exécute, il y aura de la mémoire allouée. Lorsque ces allocations atteindront un certain seuil, le GC démarrera et nettoiera pour vous ramener au niveau de base. Le cycle continuera ainsi jusqu'à ce que vous l'éteigniez. Il s'agit d'un comportement attendu.

2
awhitworth

J'ai aussi le même problème. Le client m'a signalé que la mémoire de son ordinateur augmentait de plus en plus. Au début, je pensais qu'il était vraiment étrange qu'une application Web puisse le faire même si elle était accessible par un simple navigateur. J'ai remarqué que cela ne se produisait que dans Chrome.

Cependant, j'ai commencé avec un partenaire pour enquêter et à travers les outils de développement de Chrome et la tâche du gestionnaire, nous avons pu voir l'augmentation de la mémoire que le client m'avait signalée.

Ensuite, nous voyons qu'une fonction jquery (cadre d'animation de demande) a été chargée à plusieurs reprises pour augmenter la mémoire système. Après cela, nous avons vu grâce à ce post, un compte à rebours jquery faisait cela, car il a à l'intérieur un "SETINTERVAL" qui à chaque fois mettait à jour la date dans la mise en page de mon application.

Comme je travaille avec ASP.NET MVC, je viens de quitter ce compte à rebours de script jquery depuis BundleConfig et depuis ma mise en page, en remplaçant mon compte à rebours par le code suivant:

@(DateTime.Now.ToString("dd/MM/yyyy HH:mm"))
1
Paola Bruni