<!--  As an open source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can.
-->

### Preflight Checklist
<!-- Please ensure you've completed the following steps by replacing [ ] with [x]-->

* [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
* [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
* [X] I have searched the issue tracker for an issue that matches the one I want to file, without success.

### Issue Details

* **Electron Version:**
  * 11.1.0 and 9.3.5
* **Operating System:**
  * Windows 10 2004
* **Last Known Working Electron version:**
  * NA

### Expected Behavior
use of webContents.send + ipcRenderer.on cleans up memory after use

### Actual Behavior

In a real-world case, I have a renderer for the UI and a separate browserWindow for an audio process. The audio process monitors audio devices and reports the current volume of all devices via ipcRenderer.send (through preload contextBridge to the main process). The main process then sends this volume data over to the main UI renderer via webContents.send whereas the UI renderer listens via ipcRenderer.on (via preload contextBridge). This happens about once every 100 milliseconds. Memory use of the electron app, especially the renderer, slowly climbs to theoretical Infinitum. The starting memory is about 350MB. Within about a day, it's up to 1,500MB.

It does not matter what kind of content is passed through as arguments, nor its size (garbage collector seems to work on arguments). What matters is how often / frequently webContents.send is called and if there is an ipcRenderer.on listener for it (memory leak still exists even if there is nothing in the listener function so long as the listener exists).

### To Reproduce

Electron fiddle with a very simple example calling webContents.send every millisecond (to make the test quick... but the issue exists if you do every 100 milliseconds too but is much slower) and sending a Math.random() random number to the renderer. Memory use will increase over the course of a few minutes. I tested with a 10,000-item map as an argument as well, which made memory spike very quickly and get garbage-collected repeatedly, resulting in an average memory growth of about the same as Math.random(). This leads me to believe the problem is not the data being sent but the IPC execution itself.

[https://gist.github.com/37cde4adc11b3aa3f435e5d2698d9e05](https://gist.github.com/37cde4adc11b3aa3f435e5d2698d9e05)

### Additional Information

Additional info: Using handle/invoke seems to bypass this memory leak. However this is not ideal because renderer UI has to query main every 100 milliseconds for audio volume, and main has to locally store volume information (via another invoke / handle between main and the audio process). This creates additional VU meter lag.
shell/renderer/api/context_bridge/object_cache.cc
#include "shell/renderer/api/context_bridge/object_cache.h"
#include <utility>
#include "shell/common/api/object_life_monitor.h"
namespace electron {
namespace api {
namespace context_bridge {
ObjectCachePairNode::ObjectCachePairNode(ObjectCachePair&& pair) {
  this->pair = std::move(pair);
}
ObjectCachePairNode::~ObjectCachePairNode() = default;
ObjectCache::ObjectCache() {}
ObjectCache::~ObjectCache() = default;
void ObjectCache::CacheProxiedObject(v8::Local<v8::Value> from,
                                     v8::Local<v8::Value> proxy_value) {
  if (from->IsObject() && !from->IsNullOrUndefined()) {
    auto obj = v8::Local<v8::Object>::Cast(from);
    int hash = obj->GetIdentityHash();
    auto* node = new ObjectCachePairNode(std::make_pair(from, proxy_value));
    proxy_map_[hash].Append(node);
  }
}
v8::MaybeLocal<v8::Value> ObjectCache::GetCachedProxiedObject(