Perhaps the difference here can be described as physical vs. logical leaks. GC protects excellently against the former.
Yet just talking about "memory leaks" in general, I'd suggest that GC doesn't really offer very strong protection and might even make it harder to avoid them. This is kind of controversial so I'll try to explain it carefully (and also the safety negatives associated when GC is lacking which definitely exist).
To me the greatest benefit GC provides is not protecting against leaks but in providing safety in cases where leaks aren't the biggest deal.
Take an example of a video game. Probably people have tried some games out there written by aspiring game developers in languages that provide garbage collection which leak resources like crazy. Within a half hour of playing the game, the memory usage of a simple 2D game has risen to gigabytes, and the game is increasingly getting slower and slower with prolonged usage until it is restarted. 1 year of development and 16 updates later, the game is still leaky as ever.
These types of leaky games almost always tend to be written against garbage collectors, yet they're among the leakiest applications out there. How could that be if GC is considered by some to be a silver bullet against memory leaks?
The comparison may not be fair since these games are often developed by not-so-experienced developers. Yet I would suggest these game devs might write less leaky (but more crashy) software if they didn't use garbage collection.
The reason these leaks occur is because of rooted resources. More generally speaking, they're the result of resource mismanagement which is a problem that can occur with or without GC. A game might allocate sprites repeatedly during gameplay, but multiple areas of the codebase may reference those sprites. Failure to manually release the reference in any one of these places then leads to that dreaded logical leak with rooted resources that aren't freed at the time they are no longer needed, and not freed at all until the application is shut down.
Yet the game continues to run without crashing. It might unnecessarily loop over sprites that are no longer needed or being drawn, getting slower and slower and using up more and more memory with the accumulation of unneeded sprites. Nevertheless, it's worth noting how stable (crash-free) even these amateurish indy games are, and that's where I see GC offering a great benefit.
Dangling Pointers vs. Unfreed Resources
In a similar scenario given a native codebase that doesn't use GC, the resource would be freed by an explicit request to free the resource through any one of its references (pointers). That then causes the other references (pointers) to become invalidated (become dangling pointers). Subsequent access of those dangling pointers in some part of the game then leads to very deadly undefined behavior, typically a hard crash resulting from a segfault/access violation.
A segfault here would be a very showstopping-bug, introducing a crash: a jarring, very frustrating interruption to the gameplay. Nevertheless, it would be blatantly obvious and easy to detect and correct. That's why I think the edge in ultimately avoiding memory leaks actually goes to languages that don't use garbage collection (albeit some patches later). The easiest bugs to correct are the easiest to notice/reproduce. Instead these languages without GC pay for it with more dangerous code and undefined behavior resulting from resource mismanagement at a lower memory management level. GC languages can pay for resource mismanagement in the form of safe but deceptive and subtle leaky behavior which can fly under the radar of testing.
This can be especially annoying in extensible plugin architectures where loading a third party plugin then causes that plugin to acquire a reference to a resource in your system, say some gigantic image that spans 300 megabytes. When that plugin code (which isn't even under your control) fails to manually release the reference, the mere presence of that plugin causes the codebase to mysteriously leak 300 megabyte resources here and there at no fault of your own, only the mere presence of this third-party plugin.
Anyway, I would suggest not trying to distinguish various types of leaks so much. They're all equally annoying (actually, logical leaks tend to be the most annoying). Instead I'd try to focus on the fact that garbage collection is not a silver bullet against memory leaks. To buy into this kind of illusion is often inviting very leaky software. It pays to give special attention to concepts like soft/weak/phantom references in languages with GC upfront if there's a strong motivation to avoid memory leaks. Resource management doesn't necessarily get much easier with GC and may even get harder in some cases, it's just that mismanagement tends to lead to less deadly behavior (which can actually be both good and bad depending on the context).
It's also worth noting that the physical leaks which GC definitely avoids outright are some of the easiest to avoid, with leak detection tools like
valgrind immediately showing which line of code allocated memory that wasn't freed even after application shutdown. The logical leaks are typically far more difficult, and the ones that GC and other tools can't so effectively protect us against, only a careful design and implementation when complex resource management is involved.