Issue #22251 has been updated by headius (Charles Nutter). The implementation of WeakMap on JRuby probably behaves the same, because it uses weak references that are only cleaned along certain code paths. As you (@eregon) know, the JVM GC will vacate weak references, but handling the now-dead reference object is up to user code. That may be done lazily, via "cleaner" code in other map operations. In this example, the `keys` method may be triggering such a cleanup, and doing the same in `size` would fix your issue at the cost of always polling for dead slots. It can also be done actively, via a separate thread that waits for references to be vacated. In that case, you're waiting for the GC, thread scheduler, and various memory caches to finish cleanup and propagate the results. In both strategies, there's few guarantees that the remaining references within the map are actually occupied. In your example, having `size` run clean-up logic might fix the issue, but I would *strongly* recommend not requiring that such methods deterministically reflect the actual occupation of the map. Better to explicitly say that the size only reflects *recent* occupation of the map, and may be inaccurate until all GC-related cleanup has happened. I'd also point out that on other runtimes like the JVM, forcing a single GC may not fully vacate a weak reference, since there may be complicated chains of references to resolve or finalization/cleaner logic yet to run. The JVM spec makes no guarantees about when objects are collected, which also means there's no guarantees about when weak references are cleared. Specifying behavior like you want across all Ruby implementations would be very problematic. ---------------------------------------- Bug #22251: ObjectSpace::WeakMap#size can return a stale value https://bugs.ruby-lang.org/issues/22251#change-118584 * Author: Eregon (Benoit Daloze) * Status: Open * ruby -v: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [arm64-darwin25] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ``` $ ruby -e 'm = ObjectSpace::WeakMap.new; Thread.new { m[Object.new] = Object.new }.join; GC.start; p m.size; p m.keys; p m.size' 1 [] 0 ``` The first line is 1 but I would expect 0. Interestingly calling `#keys` seems to force an update. If I GC twice then it seems correct: ``` $ ruby -e 'm = ObjectSpace::WeakMap.new; Thread.new { m[Object.new] = Object.new }.join; GC.start; GC.start; p m.size; p m.keys; p m.size' 0 [] 0 ``` Could we decrease size whenever we "remove" an entry? Apologies if that's already known, I searched for an existing issue for this but didn't find anything. -- https://bugs.ruby-lang.org/