Garbage collection happens during runtime, real time, while managed code is running. In C++ however, we need to write destruct statements into the code. So we could say that GC is built into the code (by us). So why can't managed languages behave like this? The compiler would analyse the code and insert the destruct methods into the appropriate places in the object code. If the answer to this is that eligibility for GC for some objects become clear only during running, it might still make sense to build destruct statements into the compiled code in some cases, no? Maybe in the majority of cases?
-
"Managed code", which doesn't have a specific definition as it's primarily a Microsoftism marketing term, doesn't necessarily require garbage collection.– whatsisnameCommented Jan 7, 2017 at 20:49
-
1I believe Java uses escape analysis to optimize this in some cases. But don't it in all cases while following the usual semantics of GCed languages is clearly impossible (equivalent to solving halting problem).– CodesInChaosCommented Jan 7, 2017 at 21:35
-
2You might find Automatic Reference Counting interesting, also the RUST programming language– Erik EidtCommented Jan 7, 2017 at 21:38
-
Google for Compile-time garbage collection; there are cases (unrelated to "managed code") where that is making sense. You'll find conference papers on that. Read also gchandbook.org– Basile StarynkevitchCommented Jan 7, 2017 at 23:03
-
@whatsisname Yeah, well I though Java and C# and such deserved a common name. Managed code looks good enough a term because it alludes to the code being managed at runtime. The actions of your program are thus the sum of what you wrote plus what the runtime does.– stevieCommented Jan 8, 2017 at 21:03
5 Answers
The compiler would analyse the code and insert the destruct methods into the appropriate places in the object code.
The answer to this is the same as the answer to the second paragraph of this question, which makes a very similar statement: C++ delete vs Java GC.
Figuring out the lifetime of objects is equivalent to solving the Halting Problem, so, no, the compiler can not insert the destruct methods into the appropriate places in the object code. This is especially true for languages with closures.
But wait, C++ has closures and no GC, how does that work? Well, it doesn't: you can crash your program (or more precisely: run into undefined behavior) using closures, exactly because the compiler cannot figure out the lifetimes of your closed-over variables. Aliasing is another thing that makes such memory analysis really hard.
In general, unless your language is specifically designed (and specifically restricted) for such analysis, it is impossible.
Some JVMs perform Escape Analysis at compile-time to figure out whether a reference will escape the local scope or not, and if it doesn't, the object can be allocated on the stack instead of the heap. But, you guessed it, EA is equivalent to solving the Halting Problem.
The Azul JVM does Escape Detection: it allocates objects on the stack, and when it sees a reference escaping the local scope, it will re-allocate on the heap and patch up all existing references. This happens at runtime, and thus is not subject to the Halting Problem … but you were talking about doing it at compile-time, so this doesn't count.
-
For more info on JVM escape analysis, see Brian Goetz's excellent article Java theory and practice: Urban performance legends, revisited.– JulesCommented Jan 8, 2017 at 10:10
-
Thanks for mentioning escape detection. I'm experimenting with implementing it but hadn't heard the term before. Commented Jan 8, 2017 at 11:03
-
Perhaps a practical application for static garbage analysis would be to complement a runtime GC by reducing the number of objects and references the runtime needs to be aware of? Commented Jul 14, 2020 at 17:45
-
@JamesM.Lay Yes, absolutely. That is how escape analysis works in practice. The compiler can't be perfectly sure whether something escapes, but often-times it can be sure it doesn't, and then it doesn't need to allocate it on the heap. Halting problems is solvable and can still be useful if you allow for a "not sure" answer. In fact I bet the vast majority of escape-analysis targets are very simple: allocate object, use it, return without passing it to any unknown code. Even using such a simple heuristic could provide a performance boost. Commented Aug 4, 2022 at 15:55
In C++ however, we need to write destruct statements into the code.
That is not correct. In C++, scope-bound object creation releases the programmer from that burden. The cases where one needs to use explicit "new" and "delete" statements are mostly the cases where the lifetime of the object can only determined at run time (so it cannot be figured out by the compiler where the correct "delete" statement needs to be place or when it has to be called).
So why can't managed languages behave like this?
So putting your wrong assumption aside and interpret this part of the question as "why can't managed languages provide also a scope-bound memory management like C++" - then the answer is, they can: C++/CLI does provide such a mechanism, even for "managed" objects. And if scope-bound management does not fit, the situation becomes the same as the one above - in most cases, the information when the object has to be destructed is simply not available at compile time.
Well, I changed my statement above from "always" to "mostly", since after rethinking your question, I guess you had something like this in mind:
- an object is allocated by "new" somewhere at the beginning of the local scope
- the object is used only within that scope, and nowhere else
- when the scope is left, the object could immediately be destructed and deallocated, without the need to pass it over to the GC to free it later
And your question is "can the compiler determine the fact the object does not leave the local scope in certain cases, and so generate an automatic destruct call at the end of the scope"? If that is your question, I think @JörgWMittag's answer is what you are looking for.
-
Your "answer" points out that a small site-note in the question is incorrect, but doesn't answer the actual question. Commented Jan 7, 2017 at 21:33
-
1@CodesInChaos: I don't think so. I am trying to point out the OPs question is unclear, because what he asks for is probably already there (or maybe he has something different in mind which he thinks is missing in managed languages?) Did you read my answer in full? Commented Jan 7, 2017 at 21:48
-
It's possible what I'm asking is already there and I'm not that experienced in C++, only wrote a couple of programs. Anyway, thanks for the clarification. By managed code I meant C# and Java.– stevieCommented Jan 8, 2017 at 20:59
Why do memory leaks exist in the first place in languages with no garbage collection?
Because it's difficult for developers to find exactly when to deallocate specific objects (or free the previously allocated memory). Even if you take a short-living variable which has an easily detectable lifespan:
Declare and assign a value
Use the variable
Destroy the variable
the code may misbehave, for instance by throwing an exception at the second line, leading to the variable not being destroyed.
It's similar for an imaginary GC-aware compiler. While some patterns could be processed by an app to identify, during the build, when a specific variable should be destroyed, handling any but the very elementary cases would require too much time for static checking.
Since you seem to use .NET, you have probably tried Code Contracts and the corresponding compiler. How long the build takes? For a Hello World, a few seconds. For a small app, several minutes. For medium scale app, hours or days or weeks. It's not that the technology itself is useless: it is very interesting and brings its benefits. But waiting for hours every time you need to recompile the app? Not a good idea.
-
3It doesn't just require too much time, it is generally undecidable when an object should be destroyed. However, a language can be structured in a way that makes this decidable (e.g. explicit lifetime management in Rust). Of course that makes the language less expressive and more complicated, which are undesirable traits for an approachable, general-purpose language like C# or Python.– amonCommented Jan 7, 2017 at 22:12
-
@amon: interesting. I thought that even in C#, there is a way, through static analysis of the source code, to decide, every time (except Reflection and dynamic), when the object won't be referenced any longer, and the only issue is that the analysis will require a lot of memory and run forever for large apps. Commented Jan 8, 2017 at 9:29
-
Back in my Java hacking days, I wrote a class "MemoryLeak", where new MemoryLeak (object) stashed away a reference to object in a place that was accessible to the garbage collector, but not to anything else - in other words, the object was a guaranteed memory leak. There are also plenty of situations where an application keeps references to objects around that could be referenced but are never going to be referenced, so that's in practice a memory leak. Commented Jan 8, 2017 at 14:06
The problem of releasing memory (or objects) is to know when exactly they can be released. An object can be referenced from multiple places at the same time. The object or its memory can be released when nobody references it anymore.
It is easy for a compiler to determine that one particular reference to an object goes away. However, one reference going away doesn't mean the object can be released, because there may be any number of other references to the object. So it's quite impossible to determine at compile time that an object can be released, at least for most objects.
There is no benefit to your approach. GC is performed by the framework at convenient times or when it cannot be postponed. You suggest to have it done at pre-determined moments. Those are likely to be more inconvenient moments.
Performance-wise the runtime option will usually be the better one. If you need the control for some real-time or small embedded system, you may want to or need to cleanup after yourself and use a traditional compiled language. But the in-between option is pointless, given what is available today.
-
2Handling more stuff at compile time so that it is a no-op at runtime is by definition faster than a runtime GC that has to scan the heap for garbage. It is true that GC can be faster than C++'s heap memory, but it is the allocation of memory, not the release that is faster. In C/C++, heap memory will become fragmented, requiring expensive data structures to track free memory regions. In contrast, a compacting GC can move objects around to clear contiguous memory regions which is then as cheap as stack memory.– amonCommented Jan 7, 2017 at 22:22
-
@amon This is all irrelevant to my point, I never claimed GC to be faster than in-line cleanups. In-line cleanups are typically performed when the system is still busy, GC is typically done in idle time, making the "faster" cleanups rather pointless, unless you have some operation that takes 100% CPU for an extended period. For most applications responsiveness will be more important than the total number of cycles an operation will take. Commented Jan 8, 2017 at 11:55