Проходя по указателям / ссылкам, я бы сказал. В принципе, вы просто смотрите, есть ли у объекта ссылки, указывающие на него (из других объектов, локальных переменных выполняемого в данный момент кода, ...). Если он не имеет, тогда ссылка на этот объект не может быть получена снова (в таких языках, как Java, по крайней мере, там, где вы не можете сделать обман указателя), поэтому обычно безопасно выбросить этот конкретный объект.
Другими используемыми (или используемыми) схемами являются, например, подсчет ссылок, где каждый объект имеет счетчик ссылок на него, который должен увеличиваться каждый раз, когда кто-то получает ссылку на этот объект, и уменьшаться каждый раз, когда кто-то теряет ссылку на него. объект. COM в Windows работает таким образом, если я правильно помню.
Java и .NET используют (среди прочего) сборку мусора поколений, где каждый объект изначально считается очень быстрым (гипотеза поколений). Затем он использует некоторые оптимизации, чтобы ускорить циклы сбора мусора и, таким образом, не нарушать работу программ. Раньше GC нередко блокировали программу во время ее работы, иногда на несколько секунд.
Кроме того, GC обычно работает только при нехватке памяти, т.е. е. слишком много мертвых объектов накопилось и требует восстановления. Вот почему большинство управляемых приложений, по-видимому, тратят гораздо больше памяти по сравнению с неуправляемыми, хотя во многих случаях большую часть этой памяти можно восстановить, запустив GC один раз.