A Trip Down Memory Lane 💾
Let's dive into the fun world of memory management, native programming languages, and managed programming languages.
Most of us here write code using managed languages like JavaScript, Python, Java, etc. There are many things that set these languages apart from more native languages like Assembly, C, or C++. One of the biggest of these things is dealing with memory. In a managed language, we don’t really think about memory. How our code interacts with physical memory and bytes and so on is fully abstracted away from us. Let’s dive into what all of this means!
Some Background
Our computers are a bit like goldfish. They don’t remember much beyond what just happened. To fix this, they rely on a storage mechanism known as memory. We can think of memory as being similar to a series of boxes:
Just like us with storage boxes, our computers will store some things for easy access later. They will read things they need right now, overwrite things that need replacing, and generally do a lot of bookkeeping on these digital boxes to keep everything neatly arranged. When we are writing code, our code relies on this same memory for everything ranging from storing our variables, maintaining application state, and handling other basic application-like things.
What makes this all a bit messy is that memory isn’t infinite. The memory available on our system isn’t reserved just for us and our code. It is shared with everything else that our computer or device is doing:
Our memory will be divided, often in a way that only makes sense to our operating system, between free regions and used regions. Unless we are developing an app that absolutely needs to have fine-grained knowledge about memory usage (for example - an antivirus, defragmenter, Sysinternals, firmware, etc.), we typically don’t have to worry about any of this. How and where the free regions of memory are located is just an implementation detail. The operating system simplifies a lot of this for us.
The responsibility on our plate is to appropriately handle any memory that our application will need to function properly. This is where the differences between native languages and managed languages really come in, so let’s see what is going on here.
Memory Management in Native Languages
When working with native languages, it is up to us to define how our application interacts with memory. Take a look at the following example written in C where we add up some numbers:
C is a unique creature of a language, but it does share similarities with JavaScript in style and structure. Do pay attention to the comments where we call out some important steps that relate to managing memory.
First, we tell our computer upfront how much memory we need to use to store our data:
This is done to ensure we don't use more memory than we asked for, for that will cause unwanted problems like crashes.
When we're done with our sequence of operations, we need to free up the memory we used up so other programs can use it:
If we forget to do this, we can end up with a memory leak. This is where our app crashes after our code keeps using up more and more memory until all the free memory is used up.
Given all of this extra work that we have to do, what is it that makes native programming languages more appealing? One word - performance. Native programming languages are designed to work directly at a low level. There are no layers of abstraction and speed bumps (in the guise of safety!) slowing things down between our code and the underlying hardware.
Memory Management in Managed Languages
In managed languages, we don’t bother with memory management at all. The same-ish example from earlier using JavaScript will look as follows:
Allocating memory and freeing-up memory are all magically taken care of for us. Pretty sweet, right?
In a managed programming language, the runtime environment takes care of memory management, garbage collection, and other low-level tasks. All of this hand holding allows us to focus on writing higher-level code faster, easier, and more securely. That is what makes managed programming languages so popular, but these extra layers of conveniences do come at the cost of slower app performance.
Till Next Time
If we had to summarize what we just saw in a wordy run-on sentence, it is this:
Native languages offer more direct control over memory (and other system resources) and can deliver optimal performance, but managed languages are much easier to code in since their runtimes handle so much for us.
There is a correlation between managed languages and high-level languages, but it isn’t airtight:
There are managed versions of C++ and other low-level languages where you get a hybrid experience more akin to a car autopilot. You can delegate tasks like memory management to the runtime, but you have the ability to bring back control and handle it yourself when needed.
Extending on the car analogy, when writing this, I kept imagining native code as driving using a manual transmission (aka stick shift) and managed code as driving using an automatic transmission. Some of the reasons drivers give for using one over the other sound eerily similar to reasons developers give for why they like native languages or managed languages.
Check out the following video if you want to get a good comparison between manual and automatic transmissions:
Anyway, I can go forever finding more connections between native and managed programming languages and other things we encounter in the real life. For example, are cats the pet equivalent of a managed language and dogs the pet equivalent of a native language? Ok, I’ll stop…
I hope you enjoyed this look at some of the memory-related goings on of what happens when we write code. There is a reason why I wrote about this particular topic, and it has to do with some future content I have planned around stacks, heaps, and the memory characteristics of arrays and linked lists. Do keep a lookout for that.
If you have any questions or comments or just want to say “Hi!”, reply to this e-mail, find me on Twitter, or post on the forums.
Cheers,
Kirupa 😀