Let's say you're writing some image editing software in Python similar to Photoshop:
And you're collaborating with a team of people to develop. The main point of the software is to edit images, and sometimes those images can be very high in resolution (4000x4000 pixels with 32-bit channels, leading to ~256 megabytes per image/layer).
In that case you might ask where you need to store or reference images. And the most obvious place is probably the layer system which is where the user adds/deletes image layers. Then the collection of layers might be owned and referenced by a "document" (which is a collection of layers). On top of that you might have smart filters which might want to reference layers to which they're being applied, along with layer styles. You have an undo system which probably wants to store undo actions which reference the layers they affect. The list goes on and on but many things might come to reference images and reference the things that are referencing the images.
When Should Images Be Freed?
And here's a question: when should the images be freed? Oddly I sometimes find GC enthusiasts turning this into some complex technical or theoretical CS question. It's not a complex question; it's not a trick question. It's a simple user-end design question.
The memory for those images should be freed when the user tells us he/she longer needs those images around, when they delete layers, close entire Photoshop documents, etc (give or take, perhaps some background thread needs to finish a tiny bit of processing first before those images are freed, but the "freeing process" begins when the user tells us they no longer need the image(s) around). That's when the memory should be freed, especially in a software where the user's document might require gigabytes of memory when they're doing very high-res work where it could become a serious usability issue if the images just linger around in memory without being freed until the entire software is closed.
But you can probably imagine if your colleagues get sloppy and start storing lifetime-extending image references all over the place without thinking, and lifetime-extending references to those things that reference images, and so forth, that you might very easily run into a scenario where a user requests to close a document and nothing gets freed because some thread or object that still persists is still referencing something that prevents all these things from being garbage collected whose lifetime is tied to the entire duration of the application.
You could find yourself looking at a mess like this as far as places storing lifetime-extending references to images (and this is not even nearly as gross as some examples I've encountered in the real world; this diagram is actually rather simple in comparison):
And that's a lot of places to forget to remove the appropriate references if we want to make sure that image is freed before the entire application is closed.
So GC is not a silver bullet against resource management of this sort for long-lived applications (not like one that does a computation and shuts down) where the lifetime of objects is tied to things the user is doing. In those cases you might not have to write low-level C code prone to errors like dangling pointers like:
But you might still need to remove object references from all the relevant containers (hash table is just one example) and background threads storing such object references and so forth in response to the user's input. It's still inevitably a manual affair because the user is the one telling us when the resources are no longer needed; it's not something that can be deduced automatically.
So one of the solutions to help prevent these types of leaks that Robert already mentioned are weak references, and I do wish more people paid attention to them for these types of applications when working with GC. They can really help a lot to prevent these types of mistakes and corresponding leaks which can be really difficult to trace down in hindsight.
Ah, OK! If it's mostly global objects that I should worry about, then it is not that difficult!
It's not just global objects. It's "persistent" objects. Those images in Photoshop might not be globally accessible. They might use dependency injection and limit scope and access by passing these images around to relevant places via parameter. Thread objects running in background might be given them via parameter and store them as member variables/references. Data structures being passed around via parameter might store references to them. But they are persistent state, as in their scope isn't tied to some local function which just computes something quickly and returns some output. In this type of software there's a notion of a persistent "application state" (ex: state local to somewhere close to the main entry point of the application), and it's not necessarily globally accessible, but it does linger around and persist for the duration of the application, and it's only going to free things (like images) under GC that it no longer references whatsoever in any part of its encapsulated members and the encapsulated members of those members and so forth along with auxiliary thread objects and what not.
So if you're writing software like this, with or without GC, you have to think clearly and carefully about resource management. That's a huge and unavoidable part of the technical design if you want to make sure it doesn't just continue to use more and more and more memory until the user shuts the whole thing down.
My question is: when should I worry about freeing objects from memory in Python?
For these types of applications, it's the same whether you're using Python or Java or C or C++ or anything else. The strategy to resource management to me begins with asking, "Who owns what?" It might be easy to think everything should "share ownership" of images that needs to reference them, but that's getting seducted by the temptations of GC. From a user-end standpoint only layers should own images. Only the document should own layers. Things of this sort. You might make one exception for the application history since it needs to make sure the necessary data is kept around to be capable of being undone depending on how you implement it (I'd find shared ownership reasonable there, but there should be a separate history per document, and not one for the entire application).
If you use weak references everywhere else that doesn't require ownership, that's already a great start. You might acquire strong references for short-lived durations in threads to ensure the objects don't get garbage-collected until the thread finishes processing it, but that's for short-lived durations inside some local function. Be very careful with where you store the persistent lifetime-extending references, because every time you do that, unless you're careful to null out/remove references at appropriate times (ex: in response to the proper user input events), that could translate to a logical leak.
Similar to C where every kind of memory deallocation is completely manual (even short-lived memory, not persistent application state), it's a nice habit to write your code to free the memory as soon or even before you even write the code to allocate it. Similarly if you're implementing like that hash table to cache objects in your example, I think it'd be a smart idea to write the code to remove the object reference from the hash table at the appropriate times if you can't use weak references, perhaps even before writing the code to insert the object to it, and test it and make sure that removal part works because it could easily fly under the radar of testing like a stealth fighter bug with silent leaks unless you very specifically test for that case. With GC I'd double up my testing efforts here because while it's not susceptible to dangling pointers whatsoever, the types of leaks you can get in exchange if you don't remove these references at the appropriate times can be very difficult to detect and trace down otherwise (the mistake might not be as "fatal" to the application but more difficult to detect since it'll appear to work fine except it's not freeing memory at the relevant time).