Firefox users complained a lot about the memory situation since the Firefox 4 release. The browser consumes endless memory if it stays idle for a while and after an allocation heavy task it doesn’t trigger a GC to shrink the memory footprint.
One of the reasons why this happened is the landing of the Bug 558451 – “Merge JSScope into JSScopeProperty, JSObject“. JSScopes were allocated with malloc and not on our JS heap. These off-heap allocations triggered most of our GCs. Without these allocations, our main GC trigger vanished over night. I did a quick fix in Bug 592007 – “TM: New Scope patch changes GC behavior in browser“. The main goal was to imitate our old GC behavior. Our off-heap allocation trigger is pretty simple: Once we hit 128MB of mallocs after the last GC trigger a new GC.
The problem was that the new trigger has to monitor and predict heap growth rather than simply add off-heap allocations. I introduced a heap-growth-factor that allows the JS heap to grow by 300% before we force another GC. So the additional trigger was based on the amount of memory that survives the GC. This number has to allow the heap to grow very fast because we don’t want to trigger a GC during a critical page-load or benchmark. With these changes, the GC behavior was almost the same as before.
Well now we can see that it was not good enough because we only trigger the GC when we allocate objects. This means that we don’t perform a GC even if we have a huge heap because the trigger limit is not reached. Running the V8 benchmark suite is an example for this bad behavior. Right at the end of the suite the splay benchmark allocates a huge splay tree. We have to increase our JS heap and the amount of reachable memory after each GC is around 300MB. Our GC trigger allows the heap to grow 3x before the next GC is performed.
So after the benchmark we end up with a heap size between 300 and 900MB and we don’t perform a GC until the trigger limit (900MB) is reached. This can be forever if you just read a blog or surf on some pages that are not JS allocation heavy. I did most of my testing on my MacBook Pro with 4GB of RAM. So I never realized this bad behavior. Recently I bought a new netbook with 1GB RAM and running the V8 benchmark suite on it was painful because afterwards my browser used up all memory and no GC made my browser useable again.
In order to make FF work on my netbook I implemented Bug 656120 – ” Increase GC frequency: GC occasionally based on a timer (except when completely idle)“. The idea is now that we perform a GC even if the trigger is not reached after 20 seconds. This should shrink the heap without hurting any allocation heavy pages like benchmarks or animations.
Nick Nethercote found that this patch fixed many other bugs that other users filed. It didn’t make it into the FF 6 release. Maybe not enough people complained about the memory bloat problem or we should just buy all release drivers a low-end netbook. Well the good thing is that the fix will be in FF7! Everybody should see a reduced memory footprint and it should definitely help users with limited devices like netbooks!
PS: This patch also reduces fragmentation but that’s for the next post!