If you are loading in textures or just about anything without the content manager.
You absolutely must call dispose on it or any texture you want to unload or before you reassign a from streamed texture to that reference. Also before you re-use that same reference even if you have disposed it, you must also call IsDisposed before reassigning it. If that call returns false you should not use it, until it returns true.
You should also never set a undisposed texture or resource to null before disposing that is a automatic gpu cpu memory leak.
If a app crashes without disposing its resources all those resources will memory leak.
You have very little actual control over the gpu or device driver and the gpu may just keep things in memory until it decides it needs room provided you have told it already the resource is disposed. In other words it make take some time before it unloads the memory itself for the resource.
The tricky thing here is there is a question of who owns the resource the gpu the gc the driver or you.
The way i did this sort of testing was to actually fill up the gpu memory on purpose id find the point were my gpu would throw a out of memory error. Then start loading and unloading textures and overwriting them ect when the gpu was just short of that point. To make sure there were no memory leaks and that the application wouldn’t crash because i was doing something improperly. Once you know how to do it right the rest is straight forward.
In doing this you will quickly realize that the gpu’s memory leaks are sneaky persistant. They carry over to other applications, as well as when you rerun the same application the second time and indeed the entire os can be bogged down by this situation. Until the computer is restarted. In fact another program can do this to your program.
There are quite a few gpu tools around i used to use msi afterburner to look at the basic gpu values but im pretty sure there are debugging tools if you search around for them. Though using the above tactic you don’t need them.