0

In attempting to determine how to most effciently perform a series of repetitive steps, I find that I know very little about memory usage in a web browser. I've read about the garbage collector and memory leaks several times before but that is still very little. The specific scenario in which I am interested is as follows.

  1. A set of data objects is retrieved from indexedDB using a getAll statement over a key range. The data is all text and would never exceed 0.25MB and is likely always less that 0.1MB.
  2. The array of data objects is passed to a function that builds a node in a document fragment and replaces a node in the DOM.
  3. As the user modifies/adds/deletes the data, the state is saved in the database using a get/put sequence, and an add if a new object is built. This is performed on one object at a time, not on the whole array.
  4. When the user is finished, the next set of data is retrieved and the DOM node is replaced again. The users can navigate forward and backward through the packets of data they build.

After the new node is built, the variable that holds the array of data objects is no longer used, and is set to null in attempt at avoiding any potential memory leaks. But I started thnking about what really happens to that data and memory allocation each time the functions are invoked.

Suppose the user rapidly clicks through a series of data packets. Each time the data is retrieved and the new nodes built and replaced in the DOM, is the same memory space being used over and over again, or does each invocation take up more space, and the memory used in the former invocations is later released by the GC, such that rapidly clicking through ten nodes temporarily takes up ten nodes worth of data? I mean this to include all the data involved in these steps and not just the DOM node being replaced.

One reason I ask is that I was considering holding the array of data objects in RAM, and updating the objects as the user makes edits/builds more obejcts, and then just using a put operation to record it in the database rather than a get/put sequence. And that made me wonder what takes place if that array is held in a property of one of the functions. Will the data allocation of the function property repeatedly be re-used with each new invocation or will it take up more space each time also, until former invocations are released?

I thought maybe that, even when the variables are set to null after the new nodes are built, the GC may take time to release the memory anyway, such that memory is always being used and it may as well hold one data packet at a time and eliminate the need for a get and waiting for the onsuccess event to update the object and then put it back again.

Of course, all of this works very quickly with the get/put sequence, but I'd like to understand what is happening regarding memory; and, if setting the variables to null and not holding the array in RAM is really saving nothing, then there is no reason to use get, and the less work done may make it less likely that, after a user works with this tool for an hour or two, there would be a memory issue.

Thank you for considering my rather novice question.


Thank you for the comments. Although they are interesting, they don't really concern what I am asking. I'm not asking about memory leaks, but only commented that the variables are being set to null at the close of the functions. They are set to null because I read that if the references to an area of memory are 'broken' it helps the GC's mark-and-sweep algorithm identify that the particular area of memory may be released. And, if some type of reference back to a variable remains, it will at least be pointing to null rather than an area of remaining data. Whether or not that is true, I don't know for sure; but that is what I read. I read many articles on memory leaks but these three, article 1, article 2, and article 3 I was able to find again easily to provide for examples. In article 3, page 5 shows setting a variable to null as a way to protect against memory leaks by making an object unreachable.

I don't understand why the question would be considered a micro optimization. It's just a novice question about how memory is used in the browser when multiple invocations of functions take place in between GC cycles. And it's just an example of what I've been examining, and does not depend on indexedDB at all, nor whether or not memory leaks really exist in properly coded applications. The description I provided likely made it confusing.

If a function employs local variables to retrieve an array of data objects from an indexedDB object store and in the transaction.oncomplete handler passes reference to that area of memory to another function that uses it to build a document fragment and replace a DOM node what happens to the data? Two functions have reference to the array and one to the fragment. If the references are manually broken by setting the variables that pointed to them to null (and even if not set to null), eventually the GC will release that memory. But, if the user clicks through rapidly, repeatedly invoking these functions in a short interval of time, that is, between GC cycles, will each invocation allocate a new area of memory for the data array and fragment, such that in between the GC cycles, there could be ten sets of data areas held in RAM waiting to be relased? If the array was held in a property of the function that retrieves it, would each invocation re-use that same area of memory or would the function property simple change its reference to a new area of memory holding the new array and breaking reference to the area of memory holding the previous array, but there would still be ten sets of data areas in between GC cycles?

As I wrote before, I was wondering if there was a way to re-use the same memory area for each invocation. If not possible and if there would always be several sets of data held in RAM waiting to be released between GC cycles, it may be beneficial to retain reference to the current data set and use it to reduce the amount of work performed by the browser to save the current state, which, in itself is an entriely separate issue but one that depends on how the memory is used by the browser.

When I observe snap shots of the memory in Firefox developer tools, memory use grows as I step through these data sets, repeatedly retrieving new data and building a new fragment to replace the DOM node; but the amount of that data is relatively small and it may just build up until the GC cycle runs. From this observation, it appears that each invocation uses a new area of data and breaks the reference to the previous area of data. Therefore, maintaining a reference to the current data set is not a memory issue because there are always many such memory areas held in RAM anyway in between GC cycles.

Nonetheless, I must have an issue of some sort because after adding 100 data packets, and navigating up an down them through Next / Previous buttons, the data usage continues to grow and nearly all in the domNode section. It starts out at 7MB total, 6MB in domNode and 5MB of that in #document. #document remains at 5MB but domNode grows to at least 150MB as do nothing but move up and down the records, retrieving data, building and replacing nodes but never making an edit to the data; and never having more than one node for it is always replacement of exactly the same size because in this test the 100 data packets are identical copies. So, just getAll, build a fragment, replace a DOM node, over and over again.

Thank you.

Gary
  • 2,393
  • 12
  • 31
  • "is no longer used, and is set to null in attempt at avoiding any potential memory leaks" --- setting it to `null` is redundant and does not help with anything. – zerkms Apr 05 '20 at 07:55
  • This question is to vague to be answered, really: you're thinking of micro-optimisations here. The problem is that micro-optimisation advices don't scale. That means that it's very contextual: specific to particular code, particular browser, etc etc. The **only** way to optimise - is to get something running, then profile/benchmark it. – zerkms Apr 05 '20 at 07:57
  • Having the GC kick in is bad in some situations, like if you are doing a graphic animation, because the collecting may take a good amount of the little your functions have (<17ms @60FPS), or in intensive synchronous data processing (operations that take more than a seconds to compute) because it will only add up on the full time. If as it seems, you only do some spare asynchronous operations, then having the GC kick in will not be noticeable, so don't worry with it. (Ps: memory leaks are when the memory can not be freed because the reference has been lost, this is a browser bug if it happens). – Kaiido Apr 05 '20 at 08:50
  • 1
    @Kaiido in JS you can make a memory leak that is not a browser bug but a coding mistake. – zerkms Apr 05 '20 at 09:05
  • @zerkms in js you are not responsible of memory allocation nor have you access to how much memory is available. Sure you can create stupid code, just like you can do a while(1) loop, but browsers have to handle both these cases. If your page uses too much it will be stopped by the browser, and the memory will be released, so the memory doesn't leak, – Kaiido Apr 05 '20 at 10:02
  • @Kaiido yes, and no. Even though you don't allocate does not mean you may create objects that you cannot reach from userland but which will be kept there forever: `new Promise(r => {}).then(() => { /* this is a leak */ })` <- here the function object in `.then()` leaks: it's there forever and you don't have tools to fix it from the runtime. – zerkms Apr 05 '20 at 10:07
  • @zerkms Once there is no reference neither to the parent Promise nor to the callback, nor to any of the *executors* your callback and your Promises would get GCed. It's actually still not a leak. https://stackoverflow.com/questions/20068467 One way I can think of now to create what could be considered as a "memory leak" is `setInterval`. If you loose the identifier, you may not have any way to clear it, and the browser would have to keep it alive, thus that would qualify as a leak. (though most browsers use an increasing int as identifier, so you can actually still "hackishly" clear it). – Kaiido Apr 05 '20 at 13:55
  • @Kaiido they analyse jquery promise, while `Promise` is a host object, with different rules. Native `Promise` would be kept until resolved, and will cause unfixable memory leak. – zerkms Apr 05 '20 at 20:19
  • @zerkms why would that be? No as long as there is no reference anymore (nor by your code neither by internals), even a native Promise will get GCed. An other way to leak memory though is createObjectURL(blob). if the generated UUID is lost no way to free the `blob`. So let's agree there are indeed ways to write code that leaks memory, for the page's lifespan. That doesn't change much of my first comment. – Kaiido Apr 05 '20 at 23:14
  • @Kaiido "why would that be?" --- because `Promise` is guaranteed as per the spec to invoke its `then` as long as it's resolved regardless if you have a reference to it or not. If you check the `Promise` spec you see its guarantees have no references to GC whatsoever. "That doesn't change much of my first comment" --- indeed, I was to picky, my apoligies. – zerkms Apr 05 '20 at 23:36
  • @Kaiido "An other way to leak memory though is createObjectURL(blob)" --- kind of, neither your previous example with `setInterval` or this with `createObjectURL` apply: because they are not part of the ES standard, but browser-specific standards. – zerkms Apr 05 '20 at 23:38
  • But to resolve your Promise, you must have a reference to its executor... I don't see anything in the specs contradicting that unreferenced Promises like your example can be collected. Could you provide a clear link? As to the 2 webAPIs I mentionned, they both apply since I always only talked about javascript and browsers (ES has some considerations for GC in the weakmap section, but they merely speak of it as *if the implementation has a GC mechanism*). – Kaiido Apr 06 '20 at 00:10

0 Answers0