If you don't want to spread the fire, you can click the link below!
Give me a thumbs up, ball ball!
What This?
Phenomenon
Reference counting is a garbage collection algorithm that tracks how many times an object is referenced. In this algorithm, each object maintains a counter that records how many pointers point to it.
When a new pointer refers to an object, the object's counter is incremented by 1, and when the pointer is freed, the object's counter is decremented by 1.
When the object's counter becomes 0, it means that the object does not have any pointers to it, which means that the object can be recycled.
Reference counting regularly scans objects in memory and reclaims objects with a counter of 0.
Advantages and disadvantages
- Advantages (fast) : The advantage of the reference counting method is that the timing of garbage collection can be very fast, because the counter of the object is updated in real time.
- Disadvantage (circular reference) : If there is a circular reference, that is, two or more objects refer to each other, but no other object refers to them, then the counters of these objects will never become 0, and they cannot be recycled, which will cause memory leakage.
Therefore, the reference counting method usually needs to be used together with other garbage collection algorithms to solve the problem of circular references.
example
Some people must be impatient to read the text! But who told me to be kind? I don't want to see you in such pain.
The composition of the counter is actually very simple, that is, there are only counters and domains.
- Domain: specifically stores the reference relationship between its own node and other nodes, that is,
自己的指针->其他节点
- Counter: counts the number of references
The two pictures below are very vivid.
At the beginning, we created the four nodes ABCD, where C is referenced by A
and then the result after modification A->C
is A->B
as follows;
have you learned it?
accomplish
pseudocode
update_ptr()
Function: used to update the pointer ptr to point to the object obj, and increase or decrease the counter value at the same time.
Notice:
The ptr here has two meanings, which means that the modification as shown in the above figure
A->C
becomesA->B
the two different pointers involved.
In addition, it is necessary to ensure that it is increased first and then decreased, so as to avoid ptr
accidental obj
deletion caused by the same object.
inc_ref_cnt()
Function: Counter increment operation.
dec_ref_cnt()
Function: Counter decrement operation.
GO implementation
Directory structure :
ReferenceCount
| |
| main.go
|
----->PKG
|
------>allocate.go
------>createData.go
------>freeLinkedList.go
------>object.go
linked listfreeLinkedList.go
FreeLinkedList
A data structure of is defined , which is a data structure based on a linked list, and the nodes in it are defined as Node
structures. The head node of the linked list is a pointer to the first node.
Node
The structure contains a data
property , which is used to store the data value of the node, and a next
property, which points to the next node. String()
The method implements the function of formatting and outputting the data value of the node.
type Node struct {
data interface{
}
next *Node
}
type FreeLinkedList struct {
head *Node
}
This FreeLinkedList
data structure provides the following methods:
insertAtEnd
: Insert a new node at the end of the linked list, and the data value of the new node is the incoming parameter.
func (list *FreeLinkedList) insertAtEnd(data interface{
}) {
newNode := &Node{
data: data}
if list.head == nil {
list.head = newNode
} else {
current := list.head
for current.next != nil {
current = current.next
}
current.next = newNode
}
}
deleteNode
: Delete a node in the linked list, the data value of the deleted node is the incoming parameter.
func (list *FreeLinkedList) deleteNode(data interface{
}) {
if list.head == nil {
return
}
if list.head.data == data {
list.head = list.head.next
return
}
prev := list.head
current := list.head.next
for current != nil {
if current.data == data {
prev.next = current.next
return
}
prev = current
current = current.next
}
}
printLinkedList
: Print the entire linked list, and output the data value of each node sequentially from the head node.
func (n *Node) String() string {
return fmt.Sprintf("%v", n.data)
}
func (list *FreeLinkedList) printLinkedList() {
current := list.head
for current != nil {
fmt.Printf("%v->", current.data)
current = current.next
if current == nil {
fmt.Print("nil\n")
}
}
}
core codeobject.go
A Object
structure , which contains a number no
, a reference counter refCnt
, a byte array data
, and an array of sub-objects children
.
type Object struct {
no string
refCnt int
data []byte
children []*Object
}
This structure provides the following methods:
getInterface
: Returns the number and the length of the data byte array. The reason why a getter method is written here is because I defineddata
the type of its storage in the linked list beforeinterface
.
Because in
Go
the language,interface{}
it is a unified type that can be used to represent any type of value.
However, since no methods or properties are defined, no properties or methodsinterface{}
can be accessed directly on it .
If you want tointerface{}
call a property on a variable of a type
, first cast it to the type of the method, then ainterface{}
variable of the type that implements some struct
that hasGetInterface
methods, and use the predicate cast to get its value. .
func (obj *Object) getInterface() (string, int) {
return obj.no, len(obj.data)
}
updatePtr
: Update a pointer to Object, update the related reference counter at the same time, remove the object pointed to by the original pointer from its sub-object array, and add it to the free list when its reference counter drops to 0.
func (obj *Object) updatePtr(oldptr *Object, ptr *Object, list *FreeLinkedList) {
if ptr == nil {
return
}
ptr.incRefCnt()
oldptr.decRefCnt(list)
obj.children = []*Object{
ptr}
}
incRefCnt
: Increment the reference counter.
func (obj *Object) incRefCnt() {
if obj == nil {
return
}
obj.refCnt++
}
decRefCnt
: Decrement the reference counter. If the reference counter drops to 0, it recursively callsdecRefCnt
the method and adds itself to the free list.
func (obj *Object) decRefCnt(list *FreeLinkedList) {
if obj == nil {
return
}
obj.refCnt--
if obj.refCnt == 0 {
for _, child := range obj.children {
child.decRefCnt(list)
}
obj.reclaim(list)
}
}
AddRef
: Add a sub-object pointer and increase its reference counter ( note here, when creating an object, I will not let refCnt become 1 ).
func (obj *Object) AddRef(ptr *Object) {
if ptr == nil {
return
}
obj.children = append(obj.children, ptr)
ptr.incRefCnt()
}
reclaim
: Add the object to the free list and print a message.
func (obj *Object) reclaim(list *FreeLinkedList) {
obj.children = nil
fmt.Printf("%s has been reclaimed\n", obj.no)
//这里就加入空闲链表中
list.insertAtEnd(obj)
}
distributeallocate.go
Implemented a memory allocation and recovery mechanism. in:
newObject
Function: Receive the number and size of the object, then try to find the available space from the free list to allocate to the object, and return the pointer of the object. If no free space is found, the triggerallocation_fail
function throwspanic
.
func newObject(no string, size int, list *FreeLinkedList) *Object {
obj := pickupChunk(no, size, list)
if obj == nil {
allocation_fail()
} else {
//注意我这里跟书本的伪代码不一样,因为我认为一开始新建的默认为0,而只要被引用了才改变refCnt
obj.refCnt = 0
return obj
}
return nil
}
pickupChunk
Function: Find whether there is enough space in the free list. Returns an object pointer if found, otherwisenil
. This function can also divide the remaining space and return it to the free list.
func pickupChunk(no string, size int, list *FreeLinkedList) *Object {
current := list.head
for current != nil {
var object interface{
}
temp := current.data
object = temp
if ms, ok := object.(*Object); ok {
oldNo, allocate_size := ms.getInterface()
if oldNo != "head" {
if allocate_size == size {
list.deleteNode(object)
return &Object{
no: no, data: make([]byte, size)}
} else if allocate_size > size {
list.deleteNode(object)
remainingChunk := &Object{
no: oldNo,
data: make([]byte, allocate_size-size),
}
list.insertAtEnd(remainingChunk)
return &Object{
no: no, data: make([]byte, size)}
} else {
allocation_fail()
}
}
}
current = current.next
}
return nil
}
mergeChunk
Function: It will traverse all objects in the free list and merge them into a large free block. The merged block is added to the end of the linked list.
func mergeChunk(list *FreeLinkedList) {
current := list.head
var totalSize int = 0
for current != nil {
var object interface{
}
temp := current.data
object = temp
if ms, ok := object.(*Object); ok {
//allocate_size可分配的
oldNo, size := ms.getInterface()
if oldNo != "head" {
list.deleteNode(object)
totalSize += size
}
}
current = current.next
}
newNode := &Object{
no: "No.0", data: make([]byte, totalSize)}
list.insertAtEnd(newNode)
list.printLinkedList()
}
Note :
The code also involves the processing of the reference count (refCnt) of the object. When the object is newly created, its reference count is initialized to 0; when the object is referenced, its reference count will increase; when the object is no longer referenced , its reference count is decremented.
When an object's reference count reaches 0, it is ready to be recycled.
Additionally, when an object is recycled, its child objects are also recursively recycled.
generate data and executecreateData.go
Use the various functions defined above to simulate the process of memory allocation.
Specifically, a function Execute
named , which accepts a parameter BASE_SPACE
indicating the initial space size that can be allocated, and then calls InitData
the function to initialize the free block list. Then, it calls Example1
the function to simulate the process of memory allocation, and finally calls mergeChunk
the function to merge the released free blocks to reduce memory fragmentation.
package PKG
import "fmt"
//书本图3.2的例子
//Num 数量,Size 总需要的空间
var Num int = 3
var Size int = 6
var heap []*Object
func Example1(Num, Size int, list *FreeLinkedList) {
avgSize := Size / Num
root := &Object{
no: "root", refCnt: 1, data: make([]byte, 0)}
for c, ch := 'A', 'A'+rune(Num); c < ch; c++ {
heap = append(heap, newObject(string(c), avgSize, list))
}
//root->A
root.AddRef(heap[0])
//root->C
root.AddRef(heap[2])
//A->B
heap[0].AddRef(heap[1])
fmt.Println("创建书本图3.2(a)中update_prt()函数执行时的情况")
fmt.Println(root)
fmt.Println(heap[0])
fmt.Println(heap[1])
fmt.Println(heap[2])
//让A->B => A->C
heap[0].updatePtr(heap[1], heap[2], list)
fmt.Println("最终结果显示正确,执行图3.2(b)的结果")
fmt.Println(root)
fmt.Printf("%p", heap[0])
fmt.Println(heap[0])
fmt.Printf("%p", heap[1])
fmt.Println(heap[1])
fmt.Printf("%p", heap[2])
fmt.Println(heap[2])
}
//BASE_SPACE:初始可分配空间大小,以一个链表节点的形式出场
func InitData(BASE_SPACE int) *FreeLinkedList {
head := &Node{
data: &Object{
no: "head"}}
node0 := &Object{
no: "No.0", data: make([]byte, 10)}
list := &FreeLinkedList{
head: head}
list.insertAtEnd(node0)
list.printLinkedList()
return list
}
func Execute(BASE_SPACE int) {
list := InitData(BASE_SPACE)
Example1(Num, Size, list)
list.printLinkedList()
mergeChunk(list)
}
In Example1
the function , the code first calculates the average size of each chunk, then loops through to create multiple chunks and adds them to a heap
slice called . Next, the code uses AddRef
the function to establish the relationship between pointers, simulating the reference relationship between objects in memory. Specifically, the code first root
points to the first block heap[0]
, then heap[0]
points to the second block heap[1]
, and finally root
points to the third block heap[2]
,
It is (a) in the figure below .
Next, the code calls updatePtr
the function to modify what the pointer points to, simulating the movement of the object in memory. Specifically, the code heap[0]
moves the object pointed to from from heap[1]
to heap[2]
, so heap[0]
should point to heap[2]
instead heap[1]
of . Finally, the code prints out the status of each block to show the process of memory allocation and pointer movement, which is (B) in the above figure .
Finally, the code calls mergeChunk
the function to coalesce freed free blocks to reduce memory fragmentation. Specifically, the code uses a loop to traverse the entire free block list, accumulates the size of each free block, and merges them into a large block. Finally, the code inserts this chunk at the end of the list and prints out the state of the list.
Results of themain.go
package main
import (
L "GCByGo/ReferenceCount/achieve/Considered/PKG"
)
func main() {
L.Execute(10)
}
Explain the result:
hateful! Got slapped in the face ( how fast can 13 seconds be? )
At the beginning, the space that can be allocated in the free list is displayed. You can see that [0 0 0 0 ....]
this data actually represents the domain, but I actually created an additional children
slice to store the reference relationship, so the domain itself has no meaning.
&{head 0 [] []}->&{No.0 0 [0 0 0 0 0 0 0 0 0 0] []}->nil
Then there is 3.2(a)
an example of creating a book map, which follows the following structure:
&{value of the name counter field[referenced address]}
Should be very intuitive, right?
&{root 1 [] [0xc000100230 0xc000100370]}
&{A 1 [0 0] [0xc0001002d0]}
&{B 1 [0 0] []}
&{C 1 [0 0] []}
Then I cleared B and put him in the free list
B has been reclaimed
The final result is correct, and 3.2(b)
the results of the execution graph, in which 0xc000100230
these values with unclear meanings are addresses, can be combined with the above results to know that our A points to the address of B at the beginning, and 0xc0001002d0
later changes to the address of C 0xc000100370
, and changes accordingly is the value of the counter
&{root 1 [] [0xc000100230 0xc000100370]}
0xc000100230&{A 1 [0 0] [0xc000100370]}
0xc0001002d0&{B 0 [0 0] []}
0xc000100370&{C 2 [0 0] []}
Look back at this picture again:
let B enter the free list:
&{head 0 [] []}->&{No.0 0 [0 0 0 0] []}->&{B 0 [0 0] []}->nil
However, following the principle of not wasting, I merged the B blocks that were merged into the free list, so I got the following result.
&{head 0 [] []}->&{No.0 0 [0 0 0 0 0 0] []}->nil