Netty源碼分析-PoolChunkList

前面我們介紹了PoolChunk以及針對page的更細粒度的PoolSubpage,其實在chunk的上層還有一個管理類:PoolChunkList,PoolChunkList負責管理多個chunk的生命週期,在此基礎上對內存分配進行進一步的優化,那它是如何去做的呢?我們來簡單的瞭解下,先看看它的幾個屬性:

    //arena,整個內存池管理的大管家
    private final PoolArena<T> arena;
    //當前List本身就是一個連表,每個List相互連接
    private final PoolChunkList<T> nextList;
    private PoolChunkList<T> prevList;
    
    // 當前list中的chunk最小使用比例  
    private final int minUsage;
    // 當前list中的chunk最大使用比例  
    private final int maxUsage;
    // 當前List中的chunk最大分配空間
    private final int maxCapacity;
     
    //chunk的頭元素
    private PoolChunk<T> head;

 

上面出現了nextList,prevList,除了chunk是被組織成一個list管理的,連list本身也組成了一個更大的list。 爲什麼這麼做了,先不急,我們先簡單的看下PoolChunkList中的方法:

     // 爲buf分配指定大小的內存  
   boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {  
    // 如果list中沒有chunk則直接返回
      if (head == null) {  
           return false;  
       }  
  
    // 我們一個一個chunk開始找  
       for (PoolChunk<T> cur = head;;) {  
           long handle = cur.allocate(normCapacity);  
           if (handle < 0) {  
    // handle < 0表示分配失敗,繼續到下一個chunk嘗試,一直到最後一個。
               cur = cur.next;  
               if (cur == null) {  
                   return false;  
               }  
           } else {  
    // 分配成功則將分配到的資源賦給ByteBuf  
               cur.initBuf(buf, handle, reqCapacity);  
    // 當前chunk的使用量超過一個上限閾值,則將其從當前list轉移到下一個list  
               if (cur.usage() >= maxUsage) {  
                   remove(cur);  
                   nextList.add(cur);  
               }  
               return true;  
           }  
       }  
   }  

 

這段代碼有一處比較重要,當一個chunk的用量超過一定的比例,會將該chunk從當前list挪到下一個list中,這樣挪有什麼好處呢? 我們知道chunk本身是從連續的內存中分配一小段連續的內存,這樣實際使用內存者讀寫很方便,然而這種策略也帶來了一個壞處,隨着內存的不斷分配和回收,chunk中可能存在很多碎片。 碎片越來越多後我們想分配一段連續內存的失敗機率就會提高。針對這種情況我們可以把使用比例較大的chunk放到更後面,而先從使用比例更小的chunk中更早,這樣成功的機率就提高了。然而光把chunk往後放是不科學的,因爲隨着內存的釋放,原先被嚴重瓜分的chunk中會存在越來越多的大塊連續內存,所以還得在特定條件下把chunk從後往前調。調整的時機當然就是在內存釋放的時候了:

 

   boolean free(PoolChunk<T> chunk, long handle) {
        //釋放空間
        chunk.free(handle);
        //如果chunk釋放後的使用率小於當前list的閾值
        if (chunk.usage() < minUsage) {
            //從當前list中移除
            remove(chunk);
            // Move the PoolChunk down the PoolChunkList linked-list.
            //把chunk往其它list轉移
            return move0(chunk);
        }
        return true;
    }

   
    private boolean move0(PoolChunk<T> chunk) {
        if (prevList == null) {
            // There is no previous PoolChunkList so return false which result in having the PoolChunk destroyed and
            // all memory associated with the PoolChunk will be released.
            assert chunk.usage() == 0;
            //如果沒有前一個list,則返回false,意味着會被釋放
            return false;
        }
        //調用前一個list的move方法
        return prevList.move(chunk);
    }

    

    private boolean move(PoolChunk<T> chunk) {
        assert chunk.usage() < maxUsage;
        
        //如果前一個list打不到要求則繼續往前移動
        if (chunk.usage() < minUsage) {
            // Move the PoolChunk down the PoolChunkList linked-list.
            return move0(chunk);
        }
        
        //達到要求把它加入到當前list
        // PoolChunk fits into this PoolChunkList, adding it here.
        add0(chunk);
        return true;
    }

看完上面兩個方法,我們會發現一個chunk的生命週期並不是在一個固定的list中的,隨着內存的分配和釋放,他也會進入到不同的list中去。這樣我們就必須得注意,兩個相鄰的PoolChunkList,前一個list的maxUsage和後一個list的minUsage的值必須得有一段交叉得值來緩衝,否則會出現某個usage在臨界值的chunk不停的在兩個list之間來回移動。比如前一個list是【0,50】則後一個list可以是【25-75】而不能是【50-75】。同時也要注意尾節點上的maxUsage一定要等於100,這樣chunk佔滿後纔不會被繼續往後挪(後面也沒有可用list了)。

 

其它方法源碼分析

計算當前List中Chunk還可以分配的字節空間大小。

private static int calculateMaxCapacity(int minUsage, int chunkSize) {
        minUsage = max(1, minUsage);

        //如果最低使用率是百分百,那麼PoolChunk用了達到100%時會加入當前List,所以這個list已經沒有任何分配空間。
        if (minUsage == 100) {
            // If the minUsage is 100 we can not allocate anything out of this list.
            return 0;
        }

        // Calculate the maximum amount of bytes that can be allocated from a PoolChunk in this PoolChunkList.
        //
        // As an example:
        // - If a PoolChunkList has minUsage == 25 we are allowed to allocate at most 75% of the chunkSize because
        //   this is the maximum amount available in any PoolChunk in this PoolChunkList.
        //計算可以分配的字節數量
        //比如最低使用率是25,那麼PoolChunk在使用25%後會加入到此List,那麼可用空間就是 chunkSize * (1-25%)
        return  (int) (chunkSize * (100L - minUsage) / 100L);
    }

 

 

    //把chunk先當前list中移除
    private void remove(PoolChunk<T> cur) {
    	//如果cur是head,這把head指向下一個元素
    	// x(head) -> y -> z
    	// y(head) -> z
        if (cur == head) {
            head = cur.next;
            if (head != null) {
                head.prev = null;
            }
        } else {
        	//如果cur不是head,這把自己去掉,使左右元素相連
        	//x(head) -> y(this)  ->z
        	//x(head) -> z
            PoolChunk<T> next = cur.next;
            cur.prev.next = next;
            if (next != null) {
                next.prev = cur.prev;
            }
        }
    }

 

//把chunk加入到當前list
    void add(PoolChunk<T> chunk) {
    	//判斷如果chunk的使用率已經超過當前list的閾值,則加入到下一個list
        if (chunk.usage() >= maxUsage) {
            nextList.add(chunk);
            return;
        }
       
       chunk.parent = this;
       //如果head爲null,把chunk放第一個位置
        if (head == null) {
            head = chunk;
            chunk.prev = null;
            chunk.next = null;
        } else {
        	//把chunk放入第一個位置
        	//head - x - y - z
        	//this(head) - 舊head - x - y -z
            chunk.prev = null;
            chunk.next = head;
            head.prev = chunk;
            head = chunk;
        }
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章