The author tosses the word "performance" around a lot even though it means different things in different environments, especially when it comes to GC.

First, the main cost a GC -- any GC -- always has is significantly increased RAM overhead. Whether RAM is cheap (and can be traded off for things like throughput/latency or convenience) or not depends a lot on the environment. If RAM is expensive -- a GC is not the answer. If it isn't, it may well be the best answer. OTOH, GCs at least potentially have an advantage that's very hard to achieve by other means, which is compaction. Everything else is a tradeoff.

Second, I think the author jumps the gun when discussing various solutions (ownership, stack allocation, reference counting) without first trying to understand the problem, and the problem is -- first and foremost -- RAM overhead. Whether or not that's a serious problem really depends on the environment.

Also, before discussing solutions, we need to understand the domain. I think that the domain is as follows. Objects belong in one of four lifecycles:

1. Permanent -- objects live for the duration of the program

2. Stack -- objects live for the duration of a single function (and the functions it calls, of course)

3. Arbitrary -- objects live for an undetermined lifetime

4. Transaction -- objects live for a well-defined, time-bounded domain scope, be it a request in a web server, a frame in a game, or a transaction in a DB.

Now, in spite of what some may think, very little of a serious program's data can live on the stack. Server machines today come with hundreds of GBs of RAM, and if you do the math, only a very, very, small portion of that can be exploited by kernel-thread stacks. Lightweight threads require growable stacks and those stacks are therefore heap objects themselves, with either arbitrary or transaction lifetime. The cost of allocating stack-scoped objects on the heap is quite low, as those are very short-lived objects that don't become tenured by the GC, but allocating on the stack is better because we'd like to avoid triggering young-gen collections, too, of we can help it (and, indeed, Java is getting value types, although mostly for a different reason[1]).

With so much available RAM, the best use an application can make of it is to cache as large a working-set from the DB in RAM. Those objects have an arbitrary lifetime and often require scalable concurrent access (or sharding, but that opens a whole new can of worms). Reference counting is not a good solution here.

Finally, transaction-scoped objects are best handled by arenas. GCs, like HotSpot's G1 can attempt to automatically determine those transaction-scoped objects, but the results are not as good. This is a tradeoff: a global GCs simplicity, vs. arenas' utility and low latency.

Personally, I think that the solution first depends on whether or not you're targeting high-performance in constrained environments (like C/C++ and Rust do). If so, a solution like ref-couting is a great one. If not, I see no room whatsoever for ref-counting (what scope do they target, and what advantage do they have over a GC in an unconstrained environment?)

In unconstrained environments I think the best approach is as follows:

1. Stack allocation (value objects) where possible.

2. A global GC for the arbitrary and permanent scope (no need to separate permanent objects as they place a relatively low burden on a GC).

3. Arenas for transaction scopes, if you're willing to sacrifice some simplicity for latency.

I don't see absolute necessity for ownership types (and certainly not reference counting) in such unconstrained environments, but they may certainly be useful in statically verifying that heap objects never reference an arena object (or arenas with wider scopes never reference shorter-lived arenas).

In constrained environments where manual memory management is required because the GC's RAM overhead is onerous, ownership types are invaluable, as is reference counting. You don't get compaction, but this may not matter as such applications are usually much more careful with managing their allocations and deallocations.

RTSJ -- Java for hard-realtime and safety-critical systems (which is getting a new spec these days) -- makes the simplicity tradeoff, and introduces arenas (scoped memory): https://www.aicas.com/cms/en/rtsj It also makes permanent objects explicit, because it can't tolerate any additional cost, even if it mostly limited to program startup.

Finally, the author mentions both immutability and large objects, but the two are often at odds. Persistent data structures that are popular in immutable-by-default languages like Clojure rely on small object with lots of pointers to reduce memory and performance costs.

[1]: Cache friendliness of arrays and other data structures.