This article is written with the aid of the free version of Google Gemini AI 3 detailed mode

If you’ve written C++, you’ll probably know what manual memory management is. The following code is an example:

struct Node {     const char* datum;     Node* prev;     Node* next; }; const char* data[] = { " 2 5 ", "413cD3", ":)", "=_=" }; Node* head; Node* tail; void buildNode(const char* datum, Node*& cur) {     cur = new Node();     cur -> datum = datum; } void buildDoublyLinkedList(const char* data[], int length, Node*& h, Node*& t) {     buildNode(data[0], h);     h -> prev = nullptr;     Node* cur = h;         for (int i = 1; i < length; i++) {         buildNode(data[i], cur -> next);         cur -> next -> prev = cur;         cur = cur -> next;     }     t = cur;     t -> next = nullptr; } void deleteDoublyLinkedList(Node*& h, Node*& t) {     // Examples Showing Unintentional Memory leak, unnoticed dangling pointers and possible double free     delete h;     delete t;     // } buildDoublyLinkedList(data, sizeof(data) / sizeof(data[0]), head, tail); useDoublyLinkedList(head, tail); deleteDoublyLinkedList(head, tail); 

Of course, nowadays very few C++ programmers will still use raw pointers anymore, because it’ll easily lead to unnoticed memory leak, overlooked dangling pointers or unintentional double free, but it’s just an example showing some blind spots of those only knowing how to write programs in Garbage-Collected languages. While they might not ever need to manually manage memory, at least those writing codes for embedded systems and/or performance-critical software will be very hard to completely avoid manual memory management. Even for those lucky enough not having to manually manage memory, knowing how it works can still help writing codes in Garbage-Collected languages with higher performance, robustness and security, at least they can still be very vulnerable to the Out Of Memory error.

Circular Reference

Consider the following code edited from some codes in a game engine, to show how circular reference can be an issue:

Battle.prototype.initialize = function(dataTroops, gameActors) {     this.gameEnemies = dataTroops.map(dataEnemy => new GameEnemy(dataEnemy));     this.gameBattlers = gameActors.concat(gameEnemies);     this.gameSprites = this.gameBattlers.map(gameBattler => {         gameBattler.sprite = new GameSprite(gameBattler);         return gameBattler.sprite;     }); }; Battle.prototype.act = function(battler, targets) {     this.actionBattler = battler;     this.actionBattler.act(targets); }; Battle.prototype.end = function() {     this.gameBattlers = this.gameEnemies = this.gameSprites = []; }; 

Let’s say every Battle object is recycled in a battle pool, and you’re playing a game where you can have multiple battles in distinct places all at once, in which you can choose which battle to focus on, causing the rest to be in real-time auto-battle. When all those battles end without starting any new battle, every Battle object will still hold a battler with its sprite, because actionBattler isn’t cleaned up yet until those Battle objects are reused in upcoming battles. This can be a problem if there are so many battles ending together without starting any new one, especially when it’s a 3D game, because a 3D sprite can consume a lot of memory, and obviously battles aren’t the only things that demand memory.

Actually, the problem can be a lot worse if it were a native iOS app, because both Objective-C and SWIFT still only use reference counting, causing all kinds of circular references to be vulnerable to memory leak. In this case, only actionBattler can still be theoretically freed, and the rest of the battlers along with their sprites will become massive memory leaks, because there are circular references among those battlers with their sprites. If a programmer doesn’t understand how memory management works, it’s only a matter of time before the game faces Out Of Memory errors.

To fix the memory leak issue, the following code can be added at the beginning of Battle.prototype.end:

Battle.prototype.end = function() {     // Adds these lines to fix memory leaks     delete this.actionBattler; // Or sets it to a null object     this.gameBattlers.forEach(gameBattler => {         delete gameBattler.sprite.battler;         delete gameBattler.sprite; // These sprites aren’t needed outside battles anyway     });     //     this.gameBattlers = this.gameEnemies = this.gameSprites = []; }; 

The “weak” keyword can also be used in Objective-C and SWIFT, but programmers need to first know what it does.

Downloading Memory From The Internet

Until now, “Download More RAM” is still just a sarcastic joke, but if one day, the internet connection speed becomes comparable with the M.2 SSD access speed, this can partially become a practical reality, even though it’d still be extremely risky. In the case of intranet, some computers might occasionally demand a lot of memory for a short time, then it’d be economical to have a single supercomputer connected to many workstations, and any workstation demanding lots of memory can temporarily download some virtual memory from that supercomputer. Or if you’ve some MMORPG servers being vulnerable to the Out Of Memory error, as long as those servers are located nearby some data centers with purely metered pricing on downloading virtual memory, this subscription can become the last line of defense. Bear in mind that, the physical downloading distance needs to be very close to make downloading memory really works, and in some cases, the most sophisticated remote direct memory access might already serve the purpose.

Actually, the distance limit due to the speed of light is just a small issue, because at least the downloaded memory can still be regarded as virtual memory using disk storage space, while the real danger is connection stability and security. It’s because now that some parts of the memory come from the internet, hackers would be a lot easier to access them, and once they’d such access, attacks like eavesdropping, privilege escalation, rootkit and man-in-the-middle attack can be done much easier and much more elusive than before. Without understanding how memory management works, if one day downloading virtual memory becomes feasible, those programmers might not realize the dangers it brings.

Alternatively, the data centers letting users download virtual memory might be down, or the internet connections could be down. Maybe it’s an accident, maybe it’s due to some hackers launching Distributed Denial-of-Services attacks, or maybe it’s just because the users somehow failed to pay for the purely metered pricing, but it really doesn’t matter. What really matters is that, if the programs accessing the downloaded virtual memory suddenly lose such access, what would happen to those programs, and those files accessed by them? While those files might have periodic backups to mitigate the risk of data corruption, programmers still need to understand memory management to know the risks.

On a side note: In the case of games, I’ve previously proposed an hybrid architecture that might replace cloud gaming decades later, because it’s built into the games right from the start. It causes the client side to just send raw inputs and configurations into the server side, with the server sending compressed and obfuscated rendering markups back to the client, while the rest will never leave the server. This completely eliminates all risks of downloading virtual memory or downloading any hardware capability in general, because in the case of games, cheaters will have to compromise the server before they can do anything, unless they’ve AI-based machine visions that are powerful enough to play for them while flawlessly disguised as a genuine human player, which is still very difficult when it comes to professional gaming.

Asynchronous Memory Allocation

Some programming languages like Kotlin can at least vaguely estimate how much memory it can still allocate from the system, as well as how much memory is needed to create an object, so such languages can sometimes avoid Out Of Memory by only creating objects when it’s likely that the system has enough memory for such memory allocations:

data class AllocationRequest<T>(     val kc: KClass<*>,     val bufferSize: Long,     val checkInterval: Long,     val timeoutInterval: Long,     val successBlock: () -> T,     val failBlock: () -> T,     val deferred: CompletableDeferred<T> ) val channel = Channel<AllocationRequest<*>>(Channel.UNLIMITED) object AsyncMemoryManager {     private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())     init {         scope.launch {             for (request in channel) processRequest(request)         }     }     private fun processRequest(request: AllocationRequest<*>) {         scope.launch {             var delayTime = 0L             while (!hasEnoughMemory(request.kc, request.bufferSize)) {                 if (delayTime >= request.timeoutInterval) {                     @Suppress("UNCHECKED_CAST")                     (request.deferred as CompletableDeferred<Any?>).complete(request.failBlock())                     return@launch                 }                 delay(request.checkInterval)                 delayTime += request.checkInterval             }             @Suppress("UNCHECKED_CAST")             (request.deferred as CompletableDeferred<Any?>).complete(request.successBlock())         }     }     private fun estimatedAvailableMemory(): Long {         val runtime = Runtime.getRuntime()         return runtime.maxMemory() - runtime.totalMemory() + runtime.freeMemory()     }     private fun estimatedNeededMemory(kc: KClass<*>): Long {         return ClassLayout.parseClass(kc .java).instanceSize()     }     private fun hasEnoughMemory(kc: KClass<*>, bufferSize: Long): Boolean {         return estimatedAvailableMemory() > estimatedNeededMemory(kc) + bufferSize     } } suspend fun <T> tryAllocate(     kc: KClass<*>,     bufferSize: Long,     checkInterval: Long,     timeoutInterval: Long,     successBlock: () -> T,     failBlock: () -> T ): T {     val deferred = CompletableDeferred<T>()     val request = AllocationRequest( kc, bufferSize, checkInterval, timeoutInterval, successBlock, failBlock, deferred )     channel.send(request)     return deferred.await() } 

I’ve to admit that this implementation’s far from perfect, and it’s actually assisted by the AI I mentioned at the top of the article. While it’s prone to unlikely race conditions and it doesn’t accurately calculate whether there’s really enough memory for allocations, this example still aims to show another way when it comes to trying to avoid the Out Of Memory error. When tryAllocate is called, channel.send will take request as the argument, then processRequest will immediately be called with this argument taken. If the memory allocation is eventually successful, then successBlock will be called, otherwise failBlock will be called instead, so it basically applies the idea of promises into memory allocation.

However, even with the knowledge of promises, this doesn’t mean that programmers with no understanding on how memory management works can just use this example code as-is, it’s not just because of the aforementioned flaws, but also because of the implications of asynchronous memory allocations. In most GC languages, the traditional way of allocating memory is synchronous, meaning that it’s either a success or an Out Of Memory Error. But in the case of asynchronous memory allocation, the object to be created might be immediately created, created just before reaching the timeout, or never managed to be created at all. Myabe the failBlock will just call System.gc and then calling tryAllocate again, with the hope that the GC can really clean up enough memory for the upcoming memory allocations; Maybe the failBlock will try to instead create an alternate object as a poor man’s version, to hope that this time it’s be successful by demanding less memory; Maybe the entire object creation will be abandoned entirely, and a totally different business logic will be run instead. All these considerations demand knowledge of memory management.

Borrowing Memory From The Future

In theory, when an object should’ve been created but it doesn’t exist yet due to not having enough memory for allocation, it can pretend that it’s already created. Then upon the 1st use of that object, if it doesn’t actually exist, that object will be created at that time, and then that already created object will be immediately used. One way to implement this is to use the option type, and the following oversimplified Kotlin code shows how this can be done:

// Upon declaration var lazyBattler: Battler? = null // // Upon calling methods lazyBattler = lazyBattler ?: Battler() lazyBattler.defend() // 

Of course, if there are still not enough memory to create lazyBattler, the code will still throw the Out Of Memory error. Instead, the codebase needs to establish a centralized API using the idea of promises, so that upon calling a method of an object, object creation will try to take place. If it’s resolved, the method will be called immediately after object creation; if it’s rejected, some follow-ups will be done, maybe it’s a NOP, maybe an exception will be thrown, maybe it’s something else entirely. By using an object container to trace every object with its id, the following code can be used:

suspend fun <T> callLazyMethod(     container: MutableMap< Long, T>,     id: Long,     methodBlock: () -> T,     kc: KClass<*>,     bufferSize: Long,     checkInterval: Long,     timeoutInterval: Long,     successBlock: () -> T     failBlock: () -> T ): T {     return if (container[id] != null) {         methodBlock()     } else {         tryAllocate(kc, bufferSize, checkInterval, timeoutInterval, successBlock, failBlock)     } } 

So the following code shows how callLazyMethod can be called, with methodBlock and successBlock being the focus:

val methodBlock = { BATTLERS[battlerId].act(targets) } callLazyMethod(     BATTLERS,     battlerId,     methodBlock,     Battler.Klass,     bufferSize,     checkInterval,     timeoutInterval,     {         BATTLERS[battlerId] = Battler(name, level)         methodBlock()     },     failBlock ) 

In this case, successBlock needs the container to map the specified id to the created object, then immediately call the method block, which also needs to access the object via the container. If the code is run on servers being able to extend the hardware memory capacity on the fly, or download more memory from the internet/intranet on the fly, then such lazy objects can try to borrow memory from the future, even enough it’s still possible to throw the Out Of Memory error. Needless to say, this is pure insanity in most situations, but when I try to apply the wisdom of memory management in real lives, you’ll know that some seemingly normal operations are indeed this insane, assuming that you already understand memory management. But before that, I’ll have to talk about using hard disk space as virtual memory first.

Borrowing Future Memory From Swap Space

Normally, when the physical memory is running out, the OS can swap some memory being least likely to be accessed frequently into some reserved portions of the hard disk, and these portions are the Swap Space. Although the fastest M.2 SSD being as fast as more than 14GB/s with 15us latency, is still nowhere comparable to the fastest DDR5-8600 being as fast as more than 130GB/s with 70ns latency, the former is still comparable to the deprecated DDR3-1600.

However, every SSD has a hardware limit called Terabytes Written(TBW), and once its limit’s exceeded, some portions of the SSD will start to become permanently read-only or even totally inaccessible, until no more data can be written into any part of the SSD. As M.2 SSD is of no exception, Swap Space should only be used as the last resort rather than the poor man’s real memory. Otherwise, even for M.2 SSD with TBW as high as 5600, frequently using a large amount of Swap Space will still drastically shorten the actual life span of M.2 SSD. Considering its price, this isn’t economical at all.

Consider a PC with 24x2GB DDR5-8600 and an 8TB M.2 SSD with 5600TBW and 16GB Swap Space, if apps frequently borrow lots of memory from the future, that 16GB Swap Space can be frequently accessed with lots of TBW used. Let’s say it’s as crazy as having 8TB I/O on the Swap Space per day on average, after 5600 / 8 = 700 days, which is nearly 2 years, the TBW will be fully used and the M.2 SSD will start to have problems. Needless to say, the expected lifespan of M.2 SSD of this caliber should be as long as 10 years or even more, so using it this way is a waste of precious resources. Bear in mind that, because of wear leveling, it’s not just the NAND flash in the Swap Space that will suffer, but actually the entire M.2 SSD will be slowly damaged, and almost no M.2 SSD will partition a Swap Space on the hardware level.

Not only that, since the latency of that M.2 SSD is 15us, while that of those memory is 70ns, such a drastic difference on the latency will lead to thrashing when the Swap Space’s frequently accessed with large amounts of data. It’s because major page fault is a lot more expensive than a minor one, and frequent access of the Swap Space will lead to lots of major page faults in a short time. If they also involve large amounts of data, then many pages of virtual memory will have to be mapped frequently into the page table as well, with the fact that those pages need lots of time to be created. Besides, lots of major page fault in a short time can lead to frequent context switching, which can constantly interrupt the I/O of the entire OS or even the hardware itself in extreme cases, so thrashing should never be taken lightly. After all, not all major OS have counterparts of ZRAM or ZSWAP, so borrowing memory from the future can be very dangerous.

The reason I have to explain these details is to show that, it’s really important for programmers to understand memory management, even if they’re just using Garbage-Collected languages only. For those only knowing how to write prompts for Generative AI to generate codes for them, without writing a single line of code themselves, the lack of understanding computer hardware can be even more disastrous. Also, since this article tries to apply some programming wisdom of memory management in real lives, you’ll have to understand memory management before reading the rest.

Memory Allocation Inequalities

To avoid the Out Of Memory Error while allocating memory for every object, the following inequalities must be held:

M + S + D >= A >= A * (1 - F) * (1 - R) >= O * N * (1 - P)

Where:

M = the total amount of physical memory

S = the total amount of Swap Space from all M.2 SSD

D = the total amount of memory downloaded from the internet

A = the total amount of memory allocated

F = the percentage of memory too fragmented to be allocated for creating any object

R = the percentage of memory allocation failed leading to resource starvation for the objects involved

O = the average amount of memory needed for object creation per object

N = the number of objects demanding memory allocation

P = the ratio of memory allocations for created objects being recycled in the object pool

There are 3 concepts not covered in this article yet – memory fragmentation, resource starvation and object pool:

Memory Fragmentation – It can be further divided into internal fragmentation and external fragmentation. Internal fragmentation will be a problem when there are lots of promises trying to asynchronously allocate memory for object creation, and this can be even more serious if those promises are also trying to borrow memory from the future. It’s because those promises themselves need to allocate small amounts of memory to be created, but if a memory page is 64KiB, lots of internal fragmentation will take place when many of those promises are created in a short time; External fragmentation can cause many objects that would’ve been created to fail to allocate enough contiguous memory, and this can eventually lead to many promises trying to create objects to be rejected instead. If many objects are created and destroyed in a short time, external fragmentation can quickly become very severe. Although both internal fragmentation and external fragmentation will be largely mitigated by the OS itself, and sometimes some Garbage-Collected languages can further help reducing internal fragmentation, both fragmentations will still be never trivial.

Resource Starvation – When memory allocation for object creation is performed asynchronously, it’s possible that some objects never managed to be created, causing the business logic needing those objects to be never run normally, ultimately causing other business logic having objects already created to throw exceptions as well. It’s especially the case when trying to borrow memory from the future, because the corresponding business logic doesn’t accept failure when first failing to create the objects needed, but rather gamble on the possibility that those objects can be created when they’re first needed. Such gambles are based on the anticipation that more physical memory will be added to the hardware on the fly, or more virtual memory will be downloaded from the internet, meaning that the whole architectural design’s the complete opposite of a fail-fast system, causing errors to be a lot harder to fix later on.

Object Pool – Let’s say you’ve a scrollable list having several hundred items, it’d be crazy to create every object for every item in the list right from the start, otherwise this can already cause the Out Of Memory error, or at least cause the list to take extremely long time to be loaded. Instead, all lightweight data of all items will be loaded into the memory in a lazy manner, but data like images will only be loaded while the corresponding items are currently displayed on the screen. Although frequent fast scrolling across lots of items will lead to frequent memory allocation and ultimately significant memory fragmentation, it’ll still better than loading several hundred images in the memory all at once.

How Different OS Handle OOM Differently

Windows, MacOS and Linux are the 3 major OS in the PC market, but they handle the Out Of Memory error differently:

Windows – When Windows detects that the system’s about to be out of memory, it’ll first compress the least likely accessed memory pages to release some memory space, then it’ll start to use the reserved Swap Space if available memory is still tight, and finally rejects memory allocation requests from programs when there are still not enough memory. In very rare cases, where the memory demands of some programs cause the OS kernel to have insufficient memory in the non-paged area, the Windows might trigger BSOD to prevent permanently damaging the system itself.

MacOS – Similar to Windows, it’ll first compress the least likely accessed memory pages to release some memory space, as well as purging file caches and temporary data in memory, then it’ll start to use the Swap Space if available memory is still tight. But the difference is that, MacOS will keep extending the Swap Space until all SSD is used, only when they’re nearly full, restartable background services will be killed by Jetsam first, then users will have to manually kill some active apps if that’s still not enough. But if the thrashing’s too severe, it might cause a kernel panic instead.

Linux – Similar to MacOS, it’ll first recycle page caches to release some memory space, then it’ll start to use the reserved Swap Space if available memory is still tight. Users will either use ZRAM to compress the least likely accessed memory pages in the physical memory before using the Swap Space, or use ZSwap to compress those pages that’ll be in the SSD. If all these aren’t enough, the OS will call the OOM killer, which will assign oom_score to all apps consuming memory, and the ones with the highest scores will be forcibly killed by SIGKILL, but apps and services having root level access can set oom_score_adj to be -1000 to prevent themselves by being forcibly killed by the SIGKILL of the OOM killer.

The OOM killer in Linux deserves special attention here, because if many apps having severe memory leak set their corresponding oom_score_adj to be -1000, then the OS can still run out of memory even after the OOM killer kills all the other apps and services, which will cause the OS to have a kernel panic. Therefore, if some computer viruses managed to use privilege escalation to gain root level access, they can use their corresponding oom_score_adj to cause a kernel panic. While modern Linux won’t be so vulnerable to this attack vector, this vulnerability should still be taken seriously.

Sometimes it’s not the computer viruses that exploit this, but rather some legit apps borrowing memory from the future like crazy, by explicitly granting root access from users upon start, and using setuid to lower their access rights to its reasonable level after changing their corresponding oom_score_adj to -1000, the OOM killer will treat it as a normal user-level app that can’t be executed by SIGKILL. In contrast, some apps insisting on always synchronously allocate memory for object creations(thus being impossible to borrow memory from the future), might never touch their corresponding oom_score_adj, causing these apps not being the cause of OOM to be killed by the OOM killer instead.

The Essence Of Debt

With this much exploration on memory management, I can finally try to apply some wisdom there in real lives, and I’ll first talk about the essence of debt. When it comes to the raw pointers in C++, every new must be paired with exactly a single free, otherwise there will either be memory leak or double free; Similarly, for every debt involving a debtor and creditor, every borrowed debt must be repaid with the amount previously agreed upon, otherwise there will either be bad debt or risk of money laundering, usury, tax audit and frozen accounts, just like the risks caused by double free.

Viewing the essence of debt via promises, the creditor makes promises that the agreed amount will be disbursed to the debtor as a loan within the specified time interval, or the loan rejection logic previously agreed upon will be executed by the debtor; Similarly, the debtor makes promises that the borrowed amount will be repaid to the creditor within the specified time interval(the aspect of interests will be covered later), or the agreed logic of refusing to repay the debt will be executed by the creditor. Just as writing promises with only resolve but not reject is the anti-pattern of happy path coding(never handling any edge cases), making debt without exception handling is just asking for troubles.

Note that what people really need isn’t money itself, but rather the goods and services(including common goods and public services maintained by the government) that they can use money to buy from the market(or use tax to sustain the government). So disregarding the free-rider problem, when a debtor needs to borrow money from the creditor, it means that the debtor needs money to buy goods and services, but failed to produce goods and provide services with equivalent or higher market values. So the creditor will lend the loan only when he/she believes that the debtor is very likely to produce goods and provide services with market values higher than the loan amount before the loan expires.

When the creditor accumulates more and more bad debt due to the default of his/her debtors, the personal liquidity of that creditor will become smaller and smaller, eventually there will be so much bad debt that he/she won’t have any personal liquidity anymore, thus being unable to buy any needed goods and services. It’s like an app keep leaking more and more memory, until there are no more memory for that app to allocate for object creation, thus either throwing the Out Of Memory error or keep failing to run the needed business logic, eventually causing the app to cease functioning.

The Essence Of Fiat Money

Before the adaptation of fiat money, money ultimately needs currently tangible entities as anchors, maybe they’re precious metal like gold, or maybe they’re a basket of daily necessities already on the market, but such anchors can never be anything in the future. On the other hand, fiat money doesn’t use any such concrete anchors, but rather use debt financing based on the national CPI and GDP to regulate the desired amount of M2, in order to avoid the problems caused by helicopter money, because now the central bank can use the policy rate to control the actual amount of M2.

Now that there’s debt involved, disregarding interest rates, those debt have to be eventually repaid, but the only way for the government to repay those debts is by tax and trade surplus in the future. However, when the scope is enlarged to the entire civilization, it’s the future tax of all governments in the world that keep repaying the debts necessary to keep fiat money working. The very fact that citizens are able to pay tax means that they’ve earned a lot more money than the tax they will pay, implying that they’ve produced goods and provided services of market value being much higher than the tax paid to the government. Therefore, the real anchors of fiat money are the goods and services that will be on the market in the future, forcing the civilization to keep producing more goods and providing more services than before, otherwise the debt will keep growing, until it’ll become so large that it’s impossible to repay anymore.

If physical memory corresponds to goods and services currently on the market, the Swap Space in the M.2 SSD corresponds to untapped natural resources pretending to the goods and services currently on the market, and the rest of the storage area in the M.2 SSD corresponds to obviously untapped natural resources, then the essence of fiat money becomes clear. In short, it’s just using asynchronous memory allocation to borrow memory from the future, based on the bet that more and more physical memory will be available on the fly, which is the same as the anticipation that the total amount of goods and services on the market will keep growing up in the future. In contrast, money using currently tangible entities as anchors strictly forbids asynchronous memory allocation, so it’s impossible for such money to borrow any goods and services from the future, meaning that any reckless object creation can lead to an instant OOM.

This can be easily illustrated by the 2008 financial crisis, ignoring the 50 trillion USD economic bubble vanished from the financial market, 19 trillion USD of global household wealth vaporized into thin air. On the other hand, how much worth of natural resources were destroyed? How much worth of goods were gone? How much worth of services were down? Probably nowhere near a trillion at most. This means that, even with a relatively conservative estimate, at least 18 trillion USD at that time were just promises of future goods and services, but never anything already materialized.

Nevertheless, fiat money does have its merits, provided that the central bank remain professional at all times. For instance, when the CPI increase rate is obviously too high or is noticeably higher than the GDP increase rate, if it’s because of having too much M2, then the policy rate can be increased to lower the CPI more than lowering the GDP, so that the excess M2 will first be collected by the commercial banks, then go back to the central bank. While the effects of such adjustments will be greatly diminished if the increase of CPI is due to lacking liquidity of goods and services, this shouldn’t occur so frequently. In fact, only the Russo-Ukrainian War and the 2026 Iran War have caused this recently.

Also, there might be some cases where some programs really need to borrow memory from the future, and I’ll use the following probably hypothetical example to show this. Let’s say you’ve a large server room having several server clusters, with each cluster having several servers as a single unit to run a unique app. In normal web traffic, a load balancer for each cluster will be enough, but when facing extremely large web traffic, if each customer request only needs a small data transfer object, while lots of memory needs to be allocated for creating objects to process them, synchronous memory allocation will either throw the Out Of Memory error, or have to deny some customer requests. Even with asynchronous memory allocation, if no memory’s borrowed from the future, some customers will still have to wait much longer than normal. Whereas if the app borrows memory from the future under this emergency, then immediately try to download memory from other server clusters, it’ll be possible that no customer have to wait for a very long time.

Damages To The Ecosystem

Recall that even M.2 SSD is still limited by its TBW, and if the Earth Ecosystem itself is the M.2 SSD of the civilization, then the rate of writing data shouldn’t be much higher than normal usage, otherwise the lifespan of the M.2 SSD will be a lot shorter than expected. However, if the M.2 SSD is mainly used as the Swap Space, then it’s almost certain that the rate of writing data will soon become ridiculously high, causing the M.2 SSD to degrade unusually quickly. While the Earth Ecosystem can recover itself if it’s given enough time to rest, this is basically no longer the case once the civilization advanced into industrial revolution. Loosely speaking, industrial revolution allowed the civilization to extract natural resources much faster than before, thus upgrading the hard disk from HDD to SSD, making Swap Space to become somehow practical. As technologies kept advancing, the SSD was then further upgraded into the M.2 SSD.

To investigate further, let’s try to do some math using numbers given by the AI I mentioned at the top of this article:

1.       The current sustainability limit of the Earth Ecosystem is 12.3 billion GHA

2.       The current annual GHA consumed by the civilization is 22 billion GHA, causing 22 – 12.3 = 9.7 billion GHA permanent damage to the Earth Ecosystem annually

3.       The theoretical annual GHA consumed by the civilization could be 60 billion without caring the sustainability of the Earth Ecosystem, this can be reached if the civilization keeps insisting on needing infinite exponential growth

4.       If the annual GHA consumed by the civilization is still 22 billion for the next 60 years, the Earth Ecosystem will become unable to sustain the civilization anymore, causing the civilization to completely collapse

5.       Combining 2 and 4, the remaining “TBW” of the Earth Ecosystem can be roughly estimated to be 9.7 billion GHA * 60 years = 582 billion GHA

6.       Combining 3 and 5, the most pessimistic estimate of the remaining life of the civilization = 582 / (60 – 12.3) = 12.2 years

While these are just rough estimates rather than accurate calculations, and I don’t think the civilization will suddenly become insane enough to throw away its sustainability altogether, the point is still to show that, even the Earth Ecosystem itself does have its TBW limit once its sustainability limit’s exceeded by the civilization, so the point is to try to reduce the annual GHA consumed by the civilization to be way below 12.3 billion GHA before the civilization collapses. However, it’s possible that the sustainability limit of the Earth Ecosystem will drop below 12.3 billion GHA if it’s still being constantly damaged by the civilization, so 60 years being the most optimistic estimate can be overly optimistic.

As technologies keep advancing, the civilization keeps creating more and more technofossil that further damages the Earth Ecosystem, and keeps forcing more and more species to be extinct, thus threatening the biodiversity of the Earth Ecosystem. Because the wear leveling algorithms of both the Earth Ecosystem and the civilization are too weak to evenly distribute the damage across the whole world, and the reasons for the civilization to damage the Earth Ecosystem is to quickly extract lots of resources and swiftly dispose lots of wastes, the more developed areas are usually more damaged too. But since the civilization treats the ocean as its rubbish bin, some undeveloped areas also suffer from those wastes.

If these were like writing data into the M.2 SSD, it’s not only that some portions become read-only, but also that some other portions are overwritten by poisonous data that corrupt some other data in some other portions that need to read those portions being overwritten by those poisonous data. For instance, when the Pacific Ocean are filled with more and more marine debris, the Great Pacific Garbage Patch began to form, thus damaging the nearby ecosystem, causing more and more marine lives dying, more and more seafood being poisoned, and even slightly enhancing the global warming. Another example is the Amazon Rainforest, which is already significantly damaged, has been changed from a carbon sink to be a carbon source, thus further contributing to the greenhouse effect. Not only that, the flying river above the Amazon Rainforest might be eventually blocked, thus changing the rainfall patterns of South America. This can be another source of an unforeseen butterfly effect, because the Earth Ecosystem has lots of unknown chaotic properties.

Globalization As Downloading Memory From Other Data Centers

Before globalization, as long as a nation can produce goods and provide services domestically, it’d be sometimes more effective and efficient than relying on other nations that are very far away from itself. But since globalization drastically increased the effectiveness and efficiency of traveling from anywhere to anywhere else, it’s like raising the internet connection speed to be comparable to the M.2 SSD access speed, causing downloading natural resources, goods and services from other nations to become much more economical. Strictly speaking, there are many kinds of memory in the civilization, because its professions become so specialized that almost no nation can run the entire supply chain all by itself. This is the direct result of swift technological advance, causing the supply chain to be very complex and colossal.

For instance, nation A is uniquely good at producing computer chips, nation B is uniquely good at producing and maintaining ULCV needi...