Garbage collectors have functionality to deal with reference cycles. As far, as I understand, this is necessary for all languages with GC.
But I do not understand, why there can not be created language avoiding reference cycles, using some weak references, if necessary.
What are the real life examples of unavoidable reference cycles, arising in programming?
A reference cycle simply means one or more objects referencing each other, such that if you drew it out on paper with arrows representing the dependencies you would see a cycle. The (almost) simplest reference cycle is having two objects a and b that refer to each other: a.other = b b.some_attr = a.
A strong reference cycle is when two instances of classes reference each other without the proper safeties ( weak / unowned ) hence preventing the garbage collector from disposing of them once all the variables I created stopped referencing those objects.
The main advantage of the reference counting over tracing garbage collection is that objects are reclaimed as soon as they can no longer be referenced, and in an incremental fashion, without long pauses for collection cycles and with clearly defined lifetime of every object.
Reference counting garbage collection is where each object has a count of the number of references to it. Garbage is identified by having a reference count of zero. An object's reference count is incremented when a reference to it is created, and decremented when a reference is destroyed.
You can not create a programming language avoiding reference cycles, as it would be the responsibility of the application programmer, not to create the cycles. You could only create a language which requires the programmer to always take that responsibility.
It’s the fundamental design of the data structures which may allow cycles or not. E.g. in Java, a List
is a list of references, therefore, there is no problem in storing a List
in itself, directly or indirectly. But to name a more straight-forward example, with a doubly linked list, each node has a pointer to its next node and its previous node. This is already enough to form reference cycles:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ │ -next-----> │ │ -next-----> │ │ -next-----> │ │
│ Node │ │ Node │ │ Node │ │ Node │
│ │ <-previous- │ │ <-previous- │ │ <-previous- │ │
└──────┘ └──────┘ └──────┘ └──────┘
This is already forming multiple loops, short loops between two adjacent nodes via their previous
and next
references, but also indirectly between the other nodes.
To cut these loops via weak references, the designer of the Node
class would have to decide whether to make the next
or previous
reference weak. Either of them would destroy one of the fundamental functionality:
next
referencesprevious
referencesIf you declare one of the two references weak, you can not assume that a reference to one node keeps all nodes alive anymore. If next
was weak you were required to always keep a reference to the last node to prevent sudden removal of next nodes. If previous
was weak, you had to keep a reference to the first node all the time.
So requiring the developer to always cut loops via weak references would cause fundamental restrictions to the way, the software has to be designed. As another example, consider a component to which you register a listener will modify the component when an event happened (or another component having a reference to the former), therefore forming a cyclic loop. Making the listener reference weak would imply that it could suddenly disappear without a cause.
That said, even the weak references themselves are a feature that is naturally provided by garbage collectors doing graph traversal, as only those can cheaply act when discovering their existence. A reference counting system can easily free an object when it discovers that the last/only existing strong reference has been removed, but it would need additional bookkeeping of all existing weak references to clear them when the object has been freed.
This is the point, where reference counting simply makes no sense anymore. It wouldn’t be simple to implement anymore (which is the only advantage), while being inefficient at the same time, as when traversing an object graph, like iterating over the linked list, you permanently have to update reference counters (in a thread safe way if your language supports multi-threading), whereas a traversing garbage collector only has to do something when it has to check for reclaimable memory. And it only has to traverse alive objects, thus the longer it can get away with doing nothing, the less work it will have on the next cycle.
One of the core data structures in programming is graphs, which are just interconnected groups of nodes. Cycles are completely permissible in graphs. Since nodes can be coded as objects on a heap, cycles are necessary.
On a less abstract note, graphs have many practical uses. Basically, they represent networks of things: networks of friends on Facebook, networks of cities on maps, networks of computers on the Internet, etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With