
When running a Rails application in a production environment for a while, you may encounter a...
When running a Rails application in a production environment for a while, you may encounter a phenomenon where memory usage increases unexpectedly. In July 2025, I investigated the cause of this behavior and considered countermeasures, and this article summarizes my findings.
Process.warmup when a Rails application has finished booting. However, this mechanism is intended to be executed when the application has finished booting, making it difficult to use for reducing memory usage in Rails applications that are running for extended periods.MALLOC_ARENA_MAX=2 remains an effective way to reduce memory usage without rewriting any product code. This setting prevents glibc from creating numerous arenas (memory pools) one after another, and instead reuses memory within existing memory pools, thus preventing glibc from accumulating too much free memory.Hongli Lai's article "What causes Ruby memory bloat?" covers this in detail. {% embed https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html %}
According to that article, the reasons memory bloat "appears to occur" are as follows:
malloc_trim(0) ensures that memory freed by Ruby is returned to the OS, effectively reducing the process's memory usage (RSS) as seen by the OS.Though there is one important caveat:
malloc_trim(0) does not resolve the fragmentation; it merely returns the fragmented regions to the OS as-is.This is one of the reasons "Ruby has freed the memory, but malloc does not readily return it to the OS."2
Ruby 3.3.0 introduced the Process.warmup method. This method is intended to signal to the Ruby virtual machine from an application server that "the application's startup sequence has completed, making this an optimal time to perform GC and memory optimization."3
When Process.warmup is called, the Ruby virtual machine performs the following optimizations:
This cleans up objects and caches that were generated during application startup but are no longer needed, improving memory sharing efficiency in Copy-on-Write (CoW) environments.
Also, because unnecessary objects have already been collected and the heap has been compacted, malloc-side fragmentation is likely lower at this point. This makes it an ideal time to call malloc_trim(0), and a patch that calls malloc_trim(0) internally within Process.warmup has been merged.
{% embed https://github.com/ruby/ruby/pull/8451 %}
An important point is that Process.warmup is not automatically called behind the scenes like GC. It is the kind of method that should be explicitly called at an appropriate time on the application server side when a major GC would be acceptable (e.g., before forking, before worker startup). Therefore, there may not always be an appropriate time to call it in long-running Rails applications.
So how can you prevent memory bloat without using Process.warmup or malloc_trim(0)?
Online resources have recommended using jemalloc, a smarter memory allocator. However, jemalloc's repository was archived in June 2025, and it does not appear to be actively maintained. It is best to avoid adopting it for new projects. Update Apr 3rd, 2026: Meta has recently announced a renewed commitment to jemalloc. If that leads to active releases again, jemalloc may become an option again.
As an alternative, setting the environment variable MALLOC_ARENA_MAX=2 remains effective. Because:
The articles below suggest that MALLOC_ARENA_MAX=2 can cut memory usage noticeably, while increasing response time by only a few percent.
https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html
Additionally, MALLOC_ARENA_MAX=2 is the default setting on Heroku, which suggests it is a relatively safe configuration.
https://devcenter.heroku.com/changelog-items/1683
So why is MALLOC_ARENA_MAX=2 enough?:
2) sufficient to handle requests from active threads should be adequate.
For this reason, setting MALLOC_ARENA_MAX=2 usually does not cause problems, while helping reduce the amount of freed memory glibc keeps across multiple arenas.If you want to test it more carefully, compare memory usage and response time with the value unset, then with 2, 3, and 4, and see which works best for your app.
We compared the memory usage per Pod before and after setting MALLOC_ARENA_MAX=2. The solid line represents the usage after the setting was applied, and the dashed line represents the usage before. You can see the clear difference.

Investing in Infrastructure: Meta’s Renewed Commitment to jemalloc ↩
A proposal was made to "call malloc_trim(0) when a full GC is performed in Ruby to return memory to the OS," but it was not implemented because returning fragmented memory to the OS provides little benefit since the OS cannot effectively utilize it. Feature #15667: Introduce malloc_trim(0) in full gc cycles - Ruby - Ruby Issue Tracking System ↩
The background behind the introduction of Process.warmup is explained in Feature #18885: End of boot advisory API for RubyVM - Ruby - Ruby Issue Tracking System ↩
gemmaI ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...
communityHey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...
ai(yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...
aiMy laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...
githubactionsI Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...
aiI've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...
Workflows from the Neura Market marketplace related to this DeepSeek resource