Dealing with Memory Limitations

Desktop application developers count on a semi-infinite amount of memory for applications. This is a luxury not afforded to browser application developers. Browsers have two memory-related issues that must kept in mind when developing applications:

Per-tab memory limits

Browser limitations on per-tab memory are not well documented. The 64-bit Chrome browsers are known to have a 4Gb per-tab memory limit. iOS devices have more stringent limitations: empirical evidence indicates that the iPhone 6 is limited to 645Mb, the iPhone 6s to 1Gb, and the iPhone 7 to 2Gb.

To better deal with memory limits, JS9 offers a number of global parameters in the JS9.globalOpts object. New values for these parameters can be added to the JS9 preference file. (See Configuring JS9 Site Preferences for more information about preferences.) The JS9.globalOpts.maxMemory parameter specifies the largest array size (in bytes) that can be allocated in the heap to hold a FITS image. The default is currently set to 750000000 (i.e., 750Mb). It is automatically set to lower values for Chrome (500000000 or 500Mb) and mobile (300000000).

Similarly, the JS9.globalOpts.image object allows you to set the max size image section that will be created from a FITS image. The general default values are {x: 0, y: 0}, where 0 values specify unlimited section size. For Chrome, the limits are set to {x: 6144, y: 6144} while for mobile devices, they are set to {x: 4096, y: 4096}. Obviously, these are just empirical guesses on our part!

When a FITS files is displayed by JS9, the file itself is maintained in memory, which is useful in many cases:

If none of the above apply to your use of JS9, you can save memory by removing the virtual FITS file. This can be done manually by choosing the File -> free image memory option.

Alternatively, the JS9.globalOpts.clearImageMemory parameter can be used to specify when JS9 should automatically remove the FITS virtual file after displaying each image. Options include:

Garbage collection

In the early days of desktop programming, memory was allocated and freed explicitly. This allowed fine-grained control over the amount of memory being used at any one time, but often led to memory leaks if the developer forgot to free previously allocated memory.

Modern languages, including Javascript, use automatic techniques such as garbage collection (GC) to manage memory. In principle, memory gets freed when it is "no longer needed". But since the concept of "no longer needed" cannot be decided unambiguously, approximate algorithms are used.

Problems then arise because these garbage collection algorithms are only run periodically and they do not always identify all of the memory objects that can be freed. Developers often supply "hints" to the garbage collector by setting object to null or deleting large properties of an object.

Because JS9 deals with large chunks of data, garbage collection delays and misses can lead to larger than expected memory utilization. In May 2017, we found that continuously loading and closing an image was not freeing memory properly. The problem was traced to two different factors. First, especially in Chrome, the garbage collector was not properly sensing that the fileReader result memory was available for clearing. To remedy the situation, we had to delete the fileReader result object explicitly:

  fileReader = new FileReader();
  fileReader.onload(function(){
    ... do work on the fileReader.result array ...
    // give the garbage collector a hint that we're done with the array
    delete fileReader.result;
  });
  // start reading the file as a an array
  fileReader.readAsArrayBuffer(fits);
See http://stackoverflow.com/questions/32102361/filereader-memory-leak for discussion of a similar problem.

Second, we found that if a continuous Javascript loop was too tight, the GC might not be run. This was especially true of Firefox. In the example below, each LoadFITS() call is making an xhr() call to check status of a FITS file, and loading the file using JS9.Load() if necessary. Since JS9.Load() runs asynchronously, there never was enough idle time to start the GC, and memory was not released properly. This situation actually could be seen using the Performance tab in the Firefox Developer Tools: the GC was run at the start of the loop, but never run again:

  var timeout = 1000;
  setInterval(function() {
    LoadFITS("foo1");
    LoadFITS("foo2");
  }, timeout);

As a remedy, we used a longer timeout and also skipped processing for a few several cycles over time, to try to ensure that the browser had enough idle time to start the GC.

  var nloop = 0;
  var modcycle = 1;
  var loopcycle = 10;
  var timeout = 3000;
  setInterval(function() {
    nloop++;
    nmod = nloop % loopcycle;
    // skip modcycle loops to give the GC time to be run
    if( nmod > modcycle ) {
      LoadFITS("foo1");
      LoadFITS("foo2");
    }
    // timeout should be long enough to give the GC time to be run
  }, timeout);

Another possible remedy is to reload the page periodically in order to reset browser memory:

  var nloop = 0;
  var modcycle = 1;
  var loopcycle = 10;
  var timeout = 3000;
  var maxloop = 1800 * 1000 / timeout;
  setInterval(function() {
    nloop++;
    nmod = nloop % loopcycle;
    // reload page, approx every half hour
    if( nloop > maxloop ){
        window.location.href = window.location.href;
    }
    // skip modcycle loops to give the GC time to be run
    if( nmod > modcycle ) {
      LoadFITS("foo1");
      LoadFITS("foo2");
    }
    // timeout should be long enough to give the GC time to be run
  }, timeout);

If you find that JS9 is taking much more memory than you expect, especially in situations where images are being loaded in a loop please let us know and we will try to help diagnose the problem. Note that Chrome and Firefox Memory both have Memory and Performance utilities in their Developer Tools that are very useful in understanding memory allocation issues.

Alternatively, in situations where loading images in a loop is utilizing more memory than expected, you might consider embedding JS9 inside an iframe in your web page, and reloading the JS9 page periodically. This should prevent garbage collection-related memory problems, because memory is reset each time the page is loaded.

Last updated: May 29, 2017