9 ms·
How to detect if an object has been garbage collected in JavaScript
- wahern 6y agoIt's a shame that WeakMap and WeakSet aren't enumerable. Ephemeron tables in Lua are enumerable, so creating a data structure that detects when an object is gone is possible. And because Lua supports finalizers, you can trigger an event the moment the object is garbage collected.[1][2] [1] Lua also supports reviving objects. For example, if your finalizer leaks the object to the outside environment. Implementing this can be tricky, which is perhaps why JavaScript doesn't permit it. So many limitations in JavaScript exist because a particular feature would be too difficult to implement in existing implementations. [2] Of course, because Lua supports finalizers it's possible to detect GC without using ephemeron tables. But if you can't control the target object's finalizer, then ephemeron tables are handy for doing this. And you can use your own finalizer on the ephemeron table value object to detect GC, though it'll trigger one or two GC cycles behind when the key was collected.
- jgraham 6y agoIf you can observe GC from content then site behaviour will start to depend on the specifics of one GC implementation. Not only will this be a disaster for cross-browser interop, it will prevent that single engine improving its GC design going forward because doing so will break working sites. Note that this is somewhat different from a language like Lua where it isn't necessary for all existing script to work with every new iteration of the runtime without any changes. The fact that implementation details like GC timing have remained behind the veil of abstraction is one of the only reasons that JS engines have managed to evolve from being relatively slow interpreters to highly optimised JIT compilers.
- wruza 6y agoBut then, how did LuaJIT manage to do the same thing with all these semantics in place?
- wahern 6y agoI don't think the problem are the semantics, per se. It's knowing or anticipating the semantics before you ever write the first line of VM code. LuaJIT was written to provide near perfect semantic and C API fidelity to PUC Lua 5.1; semantics that were and still are quite sophisticated relative to popular languages. (Lua is a well-disguised functional language, notwithstanding that dynamic typing is no longer en vogue.) Today's modern JavaScript engines were written with the primary purpose of making then existing JavaScript constructs fast, and those constructs were few and mostly simple. (Exception: JavaScript was an early adopter of lexical closures, notwithstanding its block scoping quirk. Prototypes are conceptually simple but, like closures, rather complex from the perspective of the VM and especially a JIT engine.) They took liberties and shortcuts--all useful, but few without similarly performant alternatives--that had the effect of constraining their ability to implement newer constructs. Because nobody would demand the major browser implementations to introduce drastic architectural changes, newer language constructs were and continue to be tailored to fit the design constraints of existing implementations. Lua has accumulated so many sophisticated constructs because it's a tiny VM that is substantially rewritten with each new major version (5.1 -> 5.2 is a major version bump). And Lua isn't beholden to a strong backward compatibility guarantee; they can discard things that don't work, keeping the VM relatively simple and small and thus easier to refactor. It's notable that while LuaJIT is much faster than PUC Lua, LuaJIT is stuck at 5.1 + some 5.2 extensions. Most newer constructs and semantics added to PUC Lua since 5.1 are relatively difficult to add back into LuaJIT.
- wruza 6y agoThese changes are not really important though, in my opinion. Lua authors do research first, not engineering first (still good and oldschool, as seen from sources). LuaJIT and 5.1 found themselves embedded in many environments, because it was one of the most successful variants. And later additions to 5.2/3 which are incompatible and non-compatible with LJ/5.1 were disputable in a mailing list. Biggest non-compatible and LJ-unimplememted changes were _ENV and integers. _ENV was a cute alternative, but not a huge revolution. Integers... well, I hope Roberto and Luis know what they’re after with that. In a context of js/lj, lj is a single-variant of always compatible language, which I think shares more similarities with js in this regard, rather than with lua 5.x series.
- klodolph 6y agoApps already depend on the specifics of individual GC implementations. At least, that’s my experience working with real-time apps.
- jfkebwjsbx 6y agoReal-time and general purpose GCs does not sound like a good idea, except for very specialized GCs...
- tinus_hn 6y agoNor real-time and Javascript in a web app
- klodolph 6y agoYou might be forgetting that multimedia apps are often soft real-time.
- klodolph 6y agoTalking about soft real-time here… do you think multimedia apps should be native-only? That it’s a bad idea to put multimedia apps on the web?
- jfkebwjsbx 6y agoThe web does not imply a GC. Most multimedia in the web is hardware accelerated, SIMD accelerated via plugins or uses Wasm. Any other way would be a huge waste on resources or simply impossible to do for high resolutions.
- a1369209993 6y ago> That it’s a bad idea to put multimedia apps on the web? Yes? Obviously? The only reason Flash ever worked for anything useful (-ish depending on your opinion of video games) was that it was in practice isolated from the rest of the web browser. Javascript... isn't.
- 6y ago
- gridlockd 6y agoI do not buy into this argument. Finalizers are generally non-deterministic, regardless of implementation. Sure, sometimes that causes issues, but that is not a good argument against having them.
- wahern 6y agoThe key to understanding Lua is that the C API is a first-class citizen, and the Lua authors strive to keep the Lua scripting and Lua C API semantics symmetric (my characterization). As a general rule, anything you can do in Lua script you can do from the C API--closures, coroutines, etc--with the same logical semantics. (And using the standard C-compliant Lua C API--no C compiler extensions required.) The VM implementation, language semantics, and Lua C API strongly reflect each other and channel language design and VM implementation. Finalizers are critical for a good C API, and whether the finalizer is a lua_CFunction or an in-language function should be and is irrelevant. And a rule that didn't permit leaking would be rather brittle and error prone from the perspective a C module author. Of course, relying on ephemerons to merely detect GC collection (as opposed to their primary purpose of caching and memoizing data associated to a particular blackbox object instance) is a rather obtuse hack, and not something I've seen in practice for anything other than debugging. I don't agree that JavaScript's semantics are what have permitted JavaScript optimizing compilers to work so well. Details like the above aren't necessarily difficult to implement efficiently; they're difficult, if not impossible, to implement if the implementation architecture didn't contemplate their existence, or if the architecture was predicated on their non-existence. For awhile LuaJIT outperformed mature JavaScript engines despite having to deal with more complex semantics and more abstract constructs (e.g. stackful coroutines that were independent of the C stack, fidelity to the Lua C API, goto, variable return lists, guaranteed TCO, etc), as well all the same dynamic typing headaches as JavaScript and Python. And it did so with an appreciably smaller VM. And I have no doubt it could do it again with the benefit of another 10 years of knowledge and experience. What Lua has going in its favor is 1) the authors are only weakly committed to backwards compatibility--there are always breakages with each new version, usually small but they add up over time; and 2) a strong commitment to a symmetric C API, which means the authors have to think long and hard about language constructs and architectural details. #1 provides them the liberty to experiment with better semantics, including discarding constructs or behaviors that don't work out. #2 is a constraint that prevents them from taking certain shortcuts, such as intermingling the "C stack" with the logical language stack. The reason JavaScript only supports explicit "async" methods rather than a more seamless control flow construct is because all implementations (except DukTape?) mingle C stack semantics with the in-language stack, including especially the JIT compiler components. This chumminess isn't necessary--LuaJIT isn't particularly handicapped by avoiding it--but it's not something you can fix after the fact; it'd requiring starting an implementation from scratch, and that's probably not ever going to happen again, at least not merely for the purposes of providing better control flow semantics. VM authors tend not to appreciate how architectural details limit the range and scope of future language constructs. The Lua C API gives the Lua authors fewer degrees of freedom, which paradoxically results, I think, in them avoiding implementation dead-ends. I do agree that it's problematic to rely on certain aspects of a GC, like absolute timing in the case of mark & sweep style GC. But other aspects, like finalizers and object revival, are deliberate guarantees focused on language semantics. Another guarantee that Lua provides is that objects are destroyed in reverse order of creation, which is a useful guarantee from the perspective of a C module writer. Again, because of the nature of the Lua C API and the need to provide more than just a blackbox GC, Lua is far more deliberate about which aspects of the GC can be relied upon (and how), and which aren't their problem. Other languages avoid this--the very thought of providing any GC-specific language features is anathema--but in time invariably find themselves providing ad hoc guarantees and interfaces, or locked into accidental semantics.
- jbroman 6y agoThe more recent weakrefs addition does allow you to observe finalization: https://v8.dev/features/weak-references https://v8.dev/features/weak-references
- jwalton 6y agoHave a look at: http://www.thedreaming.org/2019/03/19/weakmaps-in-javascript/ http://www.thedreaming.org/2019/03/19/weakmaps-in-javascript...
- neetodavid 6y agoBeing able to automate tests for memory leaks is very helpful but I would much rather incorporate the browser dev tools than not when debugging memory. Doing it only in javascript feels very janky (weak maps, attaching giant array objects to things, waiting ?? amount of time for the automated GC to run). I was debugging memory leaks in some pretty large javascript products and while I did initially start with methods like this post, I only started to make reliable progress when I became familiar with the heap snapshot tool. Diagnosing problem is trivial (just filter the heap snapshot by a Class name and count the instances) and it is The Tool To Use to actually resolve it (tracing the chain of references in the retainers view) In the future if I need to write automated tests to identify memory leaks I will look into automating the Dev Tools rather than fumbling with WeakMap or performance.memory.usedJSHeapSize
- hoten 6y agoI used the chrome devtools protocol to write a memory leak detector for my game. Has helped me quite a bit. If you like I can share the source.
- gargarplex 6y ago+1.
- hoten 6y agoReplied to my comment with link.
- iaml 6y agoWould be very useful for me
- hoten 6y agoReplied to my comment with link.
- hoten 6y ago
- syspec 6y agoYou can do this explicitly with puppeteer. Given a prototype it can tell you how many love instances of that prototype are currently in the heap. For the authors use case this seems like it would work, since they mention it is part of their test suite
- bugmen0t 6y ago> Some browsers, including Chrome but not Firefox, have the ability to check the amount of Javascript memory used. So the solution to test if an object is there, is to make it sufficiently large so that it has a noticeable impact on memory. It's not observable from content, but the Firefox profiler is awesome. Check out https://profiler.firefox.com/docs/#/./memory-allocations https://profiler.firefox.com/docs/#/./memory-allocations
- treve 6y agoThere's also the WeakRef proposal: https://github.com/tc39/proposal-weakrefs https://github.com/tc39/proposal-weakrefs
- syg 6y agoWeakRef and FinalizationRegistry will ship in Chrome 84.
- Sophistifunk 6y agoI didn't know it was this close to production finally, thank Jebus!
- nayuki 6y agoFor reference, Java has PhantomReference and ReferenceQueue to let you detect when an object has been garbage-collected: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ref/PhantomReference.html https://docs.oracle.com/en/java/javase/11/docs/api/java.base... ; https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ref/ReferenceQueue.html https://docs.oracle.com/en/java/javase/11/docs/api/java.base... There's a decent chance that JavaScript will eventually copy these features and their design. So the current Java features offer a glimpse of what could happen.
- Vanit 6y agoTLDR; you can't.
- hoten 6y agoYou can, you just need to step up a level of abstraction and use the debugger protocols in v8.
- coopsmgoops 6y agoI was under the impression that if you have a reference to an object it won't be GCed. Is that not the case?
- kamikaz1k 6y agoThe article is talking about ensuring you have no dangling references, a.k.a. memory leak.
- stevula 6y agoCheck out WeakMap and related weak- data structures. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- chrismsimpson 6y agoHow to detect if it's garbage: Is it written in JavaScript?
- chrismsimpson 6y agoOf course, depends which version you're talking about ;)
- chrismsimpson 6y agoTo write a language with only borrowing would be quite easy, everyones just to lazy too do it
- Kiro 6y ago> If you are writing an application in Javascript, soon you will have to worry about memory leaks. I run a massive Node application and never had any issues with memory leaks. I wouldn't even know how to forcefully create one even if I wanted to. I just code and never think twice about GC. How do people end up in situations where they need to start using stuff like WeakMaps?
- seanwilson 6y agoMaybe it's more common on the frontend? Complex UIs are a hotbed for leaks, where it can be a nightmare to figure out they're happening and the cause e.g. a previous screen or UI component not being destroyed properly so a table of data you generated to be displayed doesn't get collected. Just search the React and Angular GitHub issues for "memory leak".
- winrid 6y agoIt depends on your application. I'm working on an application in Node right now that keeps hundreds of megabytes of configuration in memory since each model can take seconds to generate, and due to recent changes the # of models doubled. It's not a good design. I've been trying to get buy in on using an LRU cache... Also, I've worked in a few environments (Java and Node) where a spike of requests come into the system and prevent the GC from running - and then you run out of memory. I think that mostly went away with G1. We had a lot of stuff that ran on our customers sites and was prone to traffic spikes... I've learned that if you're not thinking about GC it usually bites you later. Better to think about performance, at least a little, up front.
- hombre_fatal 6y ago> I run a massive Node application and never had any issues with memory leaks. I wouldn't even know how to forcefully create one even if I wanted to. (1) I have no leaks and (2) I don't know how to create them -- These don't really square with each other. Leaks are easy. TFA mentions the classic one in UI apps: when you don't remove event listeners.
- Kiro 6y ago
- winrid 6y agoIs there anything like VisualVM for NodeJS? It's nice in JVM land that you can just see how many objects of a particular class are on the heap. Chrome's devtools kind of work, but it's not nearly as good. I'd love to be able to see how many objects exist with a certain signature, or prototype. I found some mdb_v8 thing but it seems it's built for a very old version of V8.
- alex7o 6y agoI haven't tired that yet, but it should be possible to use graal.js, which is integrated with node. The should allow using visual VM on node.
- MH15 6y agoThis looks very interesting... NodeJS is decently performant out of the box but I'd love to have the capability to run advanced profiling tools.
- alkonaut 6y agoI was assuming a WeakMap is just a map (hash table) from a key to a WeakRef? What is it if it isn't that (Explain like I'm a 5 year old person who doesn't understand dynamic types or JS)? I read here that WeakRef is only a proposal yet, so clearly a WeakMap/WeakSet can't make use of a WeakRef...
- stestagg 6y agoIt’s a map from a ‘WeakRef’ to a value. So if you have an object reference, you can associate a value with that object. But not the other way round
- alkonaut 6y agoThanks. That does feel a bit backwards, for the purpose of GF tracking as you can’t store id->obj. I assume if/when a WeakRef is exposed in the language then a normal map can be used to store id->WeakRef(obj), so no real need them for also having a “mapWithWeakRefValues”?
- ghusbands 6y agoA WeakMap works with objects as keys. You can do it with objects you don't control, without adding some sort of id field. It can't be fully simulated with a WeakRef. One common use is to associate extra data with objects, without attaching it to those objects directly. In your map, you add an obj->extradata mapping. Having the target be a WeakRef would just mean you'd lose your extradata while the source obj is still around.
- sfink 6y agoNot quite. First, a map from a WeakRef to a value wouldn't do anything useful, since a WeakRef's target dying does not magically blow away the WeakRef itself. Second, you would have no way of getting from an object to its WeakRef(s), so you couldn't do a lookup anyway. But those criticisms are unfair; you're just using loose language to mean a map whose entries hold their keys weakly and values strongly. But it's not that either. If it were that, and your key died but your map didn't, then the value would still be kept alive. And it doesn't hold both key and value weakly; in that case, you could have both map and key alive and yet the value could die; WeakMaps won't allow that. It's something subtly different. It's a collection where both the key and map have to be live for the WeakMap entry to keep the value alive. "Weak" in the name is something of a misnomer, in my opinion. Weak normally means "something that can refer to an object without keeping it alive". WeakMap entries are not weak, they are normal strong references that very much keep their values alive -- but only if both the map and key are both alive.
- himinlomax 6y agoI was confused by his description of WeakMap, because I hadn't looked at the spec in a while. For reference, the reason why it's not applicable to the task at hand is because it's not primarily meant to have the values weakly referenced. Rather, it's meant to not lock the keys in. I'm not sure what he means by "best used to link objects together". WeakMap simply allows you to keep data about an object outside of it and without keeping it from being freed when it's not needed anywhere else.
- sfink 6y agoThat's what he means by linking objects together. Though I would describe it as you did; WeakMaps are normally used for associating data with an object, without having the WeakMap entry itself keep the object (aka the WeakMap key) alive. If the object goes away otherwise, the entry will go with it. (Or if the WeakMap goes away, then all of its entries will go away at once.) In terms of "liveness", a WeakMap entry is an "and" edge between the key and the map: the entry is only kept alive if both the key and map are still alive. (And the entry keeps the value alive, though it may not be the only thing doing so.) WeakMaps (ephemeron tables) were designed specifically to make garbage collection not be observable, for the reasons jgraham described above. If you really want to know, such as for testing, then WeakRef and FinalizationRegistry are the newer ways to do this. But know that if your app needs this stuff as part of its normal functioning, then there's at least a 90% chance you're doing something wrong. The GC does not run because you think it should run. If anything, it tries to not run, as much as possible. And the more we optimize it, the better it will get at not running. "Running slow code" is worse than "running fast code" is worse than "not running code at all". (source: I work on the SpiderMonkey GC engine.)
- holyShitGay 6y ago"Hey, guys, what I do is just reference a 1GB object, and then wait for 1GB to disappear off the heap!" Wow. A 1GB resolution blowjob hack DIY memory inspector. That's really great. Thanks.
- zomglings 6y agoIt seems like the right way to do this is by modifying your runtime. The author's solution proves their point about WeakMap but I don't see how I would use it to practically detect memory leaks in my own programs. Most importantly: > If you are writing an application in Javascript, soon you will have to worry about memory leaks. I wrote many applications in Javascript before I had to worry about memory leaks.
- cogman10 6y agoThis is all stuff you really shouldn't do. Use the profiling tools to figure out where your memory leaks exist. Doing it in code has a lot of negative consequences. Weak maps and sets, for example, will slow down your GC. The typical usage of Weak maps and sets is for caching. This allows the GC to give itself extra room on the heap at the expense of potentially another round trip or new calculation.
- gwbas1c 6y ago> If you are writing an application in Javascript, soon you will have to worry about memory leaks. What? No! Memory leaks are impossible in fully garbage collected environments. A memory leak is when you manually allocate memory and loose the pointer, thus making it impossible to access the memory. (Because there's no pointers in Javascript, there's no memory to leak.) What's happened is not a memory leak, it was an unregistered event handler. These are best diagnosed with profilers, not gymnastics with weak references. (It's not a memory leak because there is still a valid reference through the event handler, ie, no lost pointer.)
- jonathanlydall 6y agoEven if there is a more correct technical term for this, I think you’re trying to fight back an ocean here. If you have a function which each time inadvertently creates a new object in memory which it never de-references (thus is never GC’d), repeated use of the function will ultimately result in memory exhaustion and termination of the process. Everyone I’ve ever met would refer to that as a memory leak.