more details on jscript memory leak from DOM object references

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Mark D. Anderson

    more details on jscript memory leak from DOM object references

    About a month ago Richard Cornford did an interesting analysis of a
    memory leak
    in jscript (internet explorer) when there are "circular" references
    between
    DOM objects and (real) jscript objects:



    This message summarizes some testing I've done and their results.
    These results somewhat contradict Cornford's conclusions; I haven't
    analyzed his
    test page to come to an explanation.

    Below is an html test page so that anyone can (attempt to) reproduce
    my results.
    To test:
    - Bring up task manager and a fresh IE process on this web page.
    - Click on one of the div links, refresh the page, repeat, and watch
    the process size.
    I have been doing 3 refresh/click sequences per div.

    Because the leaked object is at least 1Mbyte, it should be apparent
    when the leak happens.
    (I see leaks in increments of 2Mbyte, presumably because the
    characters
    in the 10^6 long string are stored as 2 bytes each, for unicode.)

    I did my testing on IE 6.0.
    I also ran the tests on Mozilla 1.1. I discovered that the largeText
    function
    is much slower to execute on Mozilla than IE, but I found no leaks.

    question: Does the leak occur if an event handler function refers to
    only global js objects?
    test: leak_test_globa l
    answer: No.
    Apparently references to global variables (scope "window") are not
    implemented in
    the same way as lexical closures on local variables.

    question: Does the leak happen if an event handler function refers to
    a local js object?
    test: leak_test_local
    answer: Yes.

    question: Does the leak happen without true circularity, just mutual
    references somewhere between DOM and JS?
    test: leak_test_2node s
    answer: Yes (true circularity is not required).

    question: Does the leak occur with just a one-way reference from a DOM
    object to a JS object?
    test: leak_test_domre f
    answer: Yes (not even mutual reference is required).

    question: Does the leak occur with just a one-way reference from a JS
    object to a DOM object?
    test: leak_test_jsref
    answer: No.

    question: Are all JS objects leaked which are reachable by transitive
    reference from one bound by lexical closure?
    test: leak_test_reach able
    answer: Yes.

    question: Does Node.attachEven t also leak?
    test: leak_test_attac hEvent
    answer: Yes

    -mda

    ------- testleak.html -----
    <html>
    <!-- Tests of internet explorer leaks. See discussion in
    comp.lang.javas cript, 2003-7-24 -->
    <head>
    <script type="text/javascript">
    function largeText(len, s) {
    if (!s) s = '0123456789';
    var a = [];
    for(var i=len/s.length;i--;) a.push(s);
    return a.join('');
    }

    var myglobal = [];
    myglobal.big = largeText(10000 00, largeText(1000) );
    //alert("myglobal .big.length=" + myglobal.big.le ngth);

    function test_start(name ) {
    alert("performi ng test '" + name + "'");
    }

    function leak_test_globa l(node) {
    test_start('glo bal');
    node.onclick = function() {alert("length: " + myglobal.length )};
    myglobal.push(n ode); // make circular
    }

    function leak_test_local (node) {
    test_start('loc al');
    var mylocal = myglobal;
    node.onclick = function() {alert("length: " + mylocal.length) };
    mylocal.push(no de);
    }

    function leak_test_2node s(node) {
    test_start('2no des');
    var mylocal = myglobal;
    var node2 = document.create Element('div');
    document.body.a ppendChild(node 2);
    node.onclick = function() {alert("length: " + mylocal.length) };
    mylocal.push(no de2);
    }

    function leak_test_domre f(node) {
    test_start('dom ref');
    var mylocal = myglobal;
    node.onclick = function() {alert("length: " + mylocal.length) };
    }

    function leak_test_jsref (node) {
    test_start('jsr ef');
    myglobal['foobar'] = node;
    }

    function leak_test_reach able(node) {
    test_start('rea chable');
    var mylocal = {stuff : {bother: myglobal}};
    node.onclick = function() {alert("length: " +
    mylocal.stuff.b other.length)};
    mylocal['mynode'] = node;
    }

    function leak_test_attac hEvent(node) {
    test_start('att achEvent');
    if (!node.attachEv ent) {alert("no attachEvent"); return;}
    var mylocal = myglobal;
    node.attachEven t('onclick', function() {alert("length: " +
    mylocal.length) });
    // reference seems unnecessary
    //mylocal.push(no de);
    }
    </script>
    </head>
    <body>

    <div onclick="leak_t est_global(this )">leak_test_gl obal (expect:
    PASS)</div>
    <div onclick="leak_t est_local(this) ">leak_test_loc al (expect:
    FAIL)</div>
    <div onclick="leak_t est_2nodes(this )">leak_test_2n odes (expect:
    FAIL)</div>
    <div onclick="leak_t est_domref(this )">leak_test_do mref (expect:
    FAIL)</div>
    <div onclick="leak_t est_jsref(this) ">leak_test_jsr ef (expect:
    PASS)</div>
    <div onclick="leak_t est_reachable(t his)">leak_test _reachable (expect:
    FAIL)</div>
    <div onclick="leak_t est_attachEvent (this)">leak_te st_attachEvent
    (expect: FAIL)</div>
    <br>
    <div onclick="Collec tGarbage()">Col lectGarbage (expect: never does
    anything)</div>
    </body>
    </html>
  • Richard Cornford

    #2
    Re: more details on jscript memory leak from DOM object references

    "Mark D. Anderson" <mda@discerning .com> wrote in message
    news:b950119f.0 307241406.52dd6 4ff@posting.goo gle.com...[color=blue]
    >About a month ago Richard Cornford did an interesting
    >analysis of a memory leak
    >in jscript (internet explorer) when there are "circular"
    >references between DOM objects and (real) jscript objects:[/color]

    <snip URL of previous discussion via groups.google.c om>
    [color=blue]
    >This message summarizes some testing I've done and their
    >results. These results somewhat contradict Cornford's
    >conclusions; I haven't analyzed his test page to
    >come to an explanation.
    >
    >Below is an html test page so that anyone can (attempt
    >to) reproduce my results.[/color]
    [color=blue]
    > To test:
    > - Bring up task manager and a fresh IE process on this
    >web page.
    > - Click on one of the div links, refresh the page, repeat,
    >and watch the process size.
    >I have been doing 3 refresh/click sequences per div.[/color]

    I am not going to have time to examine your test page in detail tonight
    (and maybe not tomorrow either) to see if and why you think your results
    are different to mine (if you are going to claim your results contradict
    my conclusions it would have saved a bit of time if you had stated the
    specific conclusions that you think are contradicted). I should be able
    to find time to look at your page in detail over the weekend and let you
    know what I think.

    However, did you notice early in the previous thread Jim Ley implying
    that just repeatedly refreshing the same page may give the impression of
    a memory leak in IE when navigating away from the page would free the
    memory. He specifically agreed with my suggestion that any page wishing
    to demonstrate a real problem in IE would have to cycle between at least
    two distinct pages.

    While I would not take anyone's word as gospel, when it comes to
    obscurer details of browser behaviour I would always pay close attention
    to what Jim has to say. Accordingly my tests used two or three pages and
    cycled between them. As a result I am certain that they do demonstrate
    the memory leaks I described, though I may have concluded that the
    problem was more general than it actually is. But I will be
    incorporating your code into Multi-page examples for testing.

    Giving your script a cursory glance, specifically the -
    leak_test_globa l - function, I don't think that it demonstrates that
    references to global objects do not produce leaks as the DIV element
    does not have a reference to the global object and the closure formed by
    assigning the event handling function does not contain a reference to
    the global object. If you replace the event handling function with an
    expando property that refers to the global object, so that the reference
    is circular:-

    function leak_test_globa l(node) {
    test_start('glo bal');
    node.expando1 = myglobal;
    myglobal.push(n ode); // make circular
    }

    - I think you will find that it does leak.

    Richard.


    Comment

    • Richard Cornford

      #3
      Re: more details on jscript memory leak from DOM object references

      "Mark D. Anderson" <mda@discerning .com> wrote in message
      news:b950119f.0 307241406.52dd6 4ff@posting.goo gle.com...
      <snip>[color=blue]
      >question: Does the leak occur with just a one-way
      >reference from a DOM object to a JS object?
      > test: leak_test_domre f
      > answer: Yes (not even mutual reference is required).[/color]
      <snip>[color=blue]
      >function leak_test_domre f(node) {
      > test_start('dom ref');
      > var mylocal = myglobal;
      > node.onclick = function() {
      > alert("length: " + mylocal.length) };
      >}[/color]
      <snip>

      I am getting hooked (and I should be sleeping). This one is not a
      one-way reference. Assigning the inner function to onclick is forming a
      closure. That closure creates a circular reference becase its - node -
      parameter is a reference to the DIV and the DIV has a reference to the
      inner function. Those circular references preserve the closure and the
      closure contains a reference to - myglobal -, preserving it. Try nulling
      the - node - reference at the end of the function; breaking the circle.

      function leak_test_domre f(node) {
      test_start('dom ref');
      var mylocal = myglobal;
      node.onclick = function() {
      alert("length: " + mylocal.length) };
      node = null;
      }

      leak_test_reach able - and - leak_test_attac hEvent - have similar
      problems.

      Richard.


      Comment

      • Richard Cornford

        #4
        Re: more details on jscript memory leak from DOM object references

        "Mark D. Anderson" <mda@discerning .com> wrote in message
        news:b950119f.0 307252051.54a7f 491@posting.goo gle.com...[color=blue]
        >FYI I have a new version at
        >http://www.discerning.com/js/testleak.html with corrections
        >reflecting your comments, some additions, and general cleanup.[/color]

        Would you mind removing my e-mail address from that page. As I never
        respond to unsolicited e-mail from total strangers it can server no
        purpose but encourage spam.
        [color=blue][color=green]
        >>(if you are going to claim your results contradict my
        >>conclusions it would have saved a bit of time if you had stated
        >>the specific conclusions that you think are contradicted).[/color]
        >
        >Sorry. But now with your corrections, I'm not sure there are
        >contradictio ns anymore :).[/color]

        Yes, I do not see anything that contradicts my original conclusions.
        Circular references between DOM elements and JS objects do prevent
        garbage collection on IE and one-way references do not.
        [color=blue]
        >I should say too that I started with your posting not to pick
        >on you, but because it was the best I found anywhere.[/color]

        I didn't think that you did, and I am not arrogant enough to think that
        I could not have been wrong in the first place. I just would have liked
        to be able to narrow down to the specifics quickly.

        <snip>[color=blue]
        >Btw, i'm guessing that CollectGarbage( ) on IE does something
        >similar to what navigating to a new site would do;
        >neither seems to have any consequence for these tests.[/color]

        Without documentation your guess is as good as mine. Certainly
        CollectGarbage is of no help with the memory leak problem.

        <snip>[color=blue][color=green]
        >>function leak_test_globa l(node) {
        >> test_start('glo bal');
        >> node.expando1 = myglobal;
        >> myglobal.push(n ode); // make circular
        >>}
        >>
        >>- I think you will find that it does leak.[/color]
        >
        >I have added a new test, leak_test_globa lexpando which is your
        >version above, and yes, it does leak.
        >
        >What I don't understand though is what is different about
        >globals from locals that mean that a global is not trapped
        >in the closure below:[/color]

        Function local variables and parameters are stored in the execution
        context of a function call as properties of an internal object referred
        to in the ECMA spec as the "variable" object. Because inner functions
        have access to those parameters and variables after the completion of
        the execution of their outer function the "variable" object (at the very
        least) must continue to exist when a closure is formed. Thus and
        references to DOM Elements held on the "variables" object must also
        continue to exist.

        Occurrences of an identifier for a global variable within a function do
        not effect the "variable" object and will eventually be scope-resolved
        against the global object. So no references to the global variables are
        preserved within a closure unless they are also assigned to a local
        variable or parameter.

        A closure might best be perceived as a structure of JavaScript objects,
        at minimum: a function object (the inner function) and a "variable"
        object, with the function object having a property (internal) that
        refers (possibly indirectly) to the "variable" object.

        <snip>[color=blue]
        > i also don't understand by node.expando1 = myglobal traps
        >myglobal, but using node.onclick = function() {...expression
        >using myglobal...} does not.[/color]
        <snip>

        Your original function did produce a memory leak because the - node -
        parameter referred to the DOM Element and assigning the inner function
        to the event handler formed a closure. However, no references to that
        global object (with the big string) were trapped in the closure (as
        explained above) so the leek was too small be exposed by this test
        method.

        Having the DOM Element directly hold a reference to the global object
        while the global object held a reference to the Element produced a
        circular reference that did encompass the big string. No closure was
        formed in that case but it does demonstrate that global object have no
        special role in the context of this problem; all JavaScript objects
        (including the internal "variable" object) can form one point in an
        unbreakable circular reference on IE.

        I will not have an opportunity to look at your new page in detail today
        (maybe tomorrow), I will let you know if anything else occurs to me.

        Richard.


        Comment

        • Mark D. Anderson

          #5
          Re: more details on jscript memory leak from DOM object references

          > Would you mind removing my e-mail address from that page. As I never[color=blue]
          > respond to unsolicited e-mail from total strangers it can server no
          > purpose but encourage spam.[/color]

          Done; now spammers will have to be satisfied with using one of your
          1300+ posts
          to usenet :).

          [color=blue]
          > Occurrences of an identifier for a global variable within a function do
          > not effect the "variable" object and will eventually be scope-resolved
          > against the global object. So no references to the global variables are
          > preserved within a closure unless they are also assigned to a local
          > variable or parameter.[/color]

          Thanks; that helps.
          I just found this in 262-3 section 10.1.3 which is consistent
          with what you say:

          Every execution context has associated with it a variable object.
          Variables and functions declared in the source text are added as
          properties of the variable object. For function code, parameters
          are added as properties of the variable object.

          Reading some of the surrounding text, you are definitely correct that
          global code
          (and global variables) are a distinct case from function code, at any
          level of nesting.

          -mda

          Comment

          Working...