欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放

程序员文章站 2023-11-06 14:16:04
ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机。 内存分配分两个阶段: 第一阶段,初始化时分配内存。第二阶段: 内存不够用时分配新的内存。ByteBuf抽象层没有定义第一阶段的行为,但定义了第二阶段的方法: public abstract ByteBuf capac ......

  bytebuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机。

 

  内存分配分两个阶段: 第一阶段,初始化时分配内存。第二阶段: 内存不够用时分配新的内存。bytebuf抽象层没有定义第一阶段的行为,但定义了第二阶段的方法:

  public abstract bytebuf capacity(int newcapacity)

  这个方法负责分配一个长度为newcapacity的新内存。

 

  内存释放的抽象实现在abstractreferencecountedbytebuf中实现,这个类实现引用计数,当调用release方法把引用计数变成0时,会调用

  protected abstract void deallocate()

  执行真正的内存释放操作。

 

内存相关的属性

  bytebuf定义了两个内存相关的属性:

  capacity: 当前的当前的容量,也就是当前使用的内存大小。使用capacity()方法获得。

  maxcapacity: 最大容量,也就是可以使用的最大内存大小。使用newcapacity()方法获得。

 

内存分配时机

  abstractbytebuf定义了内存分配的时机。当writexx方法被调用的时候,如果如果发现可写空间不足,就调用capacity分配新的内存。下面以writeint为例详细分析这个过程。

 1     @override
 2     public bytebuf writeint(int value) {
 3         ensurewritable0(4);
 4         _setint(writerindex, value);
 5         writerindex += 4;
 6         return this;
 7     }    
 8 
 9 
10     final void ensurewritable0(int minwritablebytes) {
11         ensureaccessible();
12         if (minwritablebytes <= writablebytes()) {
13             return;
14         }
15 
16         if (minwritablebytes > maxcapacity - writerindex) {
17             throw new indexoutofboundsexception(string.format(
18                     "writerindex(%d) + minwritablebytes(%d) exceeds maxcapacity(%d): %s",
19                     writerindex, minwritablebytes, maxcapacity, this));
20         }
21 
22         // normalize the current capacity to the power of 2.
23         int newcapacity = calculatenewcapacity(writerindex + minwritablebytes);
24 
25         // adjust to the new capacity.
26         capacity(newcapacity);
27     }
28         

  3行: ensurewritable确保当前能写入4byte的数据。

  11行: 确保当前bytebuf是可以访问的。防止多线程环境下,bytebuf内存被释放后读写数据。

  12,13行: 如果内存够用,就此作罢。

  16行:  如果需要内存大于maxcapacity抛出异常。

  23, 26行行: 计算新内存的大小, 调用capacity(int)分配新内存。

  

  重新分配内存之前的一个重要步骤的计算新内存的大小。这个工作由calculatenewcapacity方法完成,它的代码如下:

 1     private int calculatenewcapacity(int minnewcapacity) {
 2         final int maxcapacity = this.maxcapacity;
 3         final int threshold = 1048576 * 4; // 4 mib page
 4 
 5         if (minnewcapacity == threshold) {
 6             return threshold;
 7         }
 8 
 9         // if over threshold, do not double but just increase by threshold.
10         if (minnewcapacity > threshold) {
11             int newcapacity = minnewcapacity / threshold * threshold;
12             if (newcapacity > maxcapacity - threshold) {
13                 newcapacity = maxcapacity;
14             } else {
15                 newcapacity += threshold;
16             }
17             return newcapacity;
18         }
19 
20         // not over threshold. double up to 4 mib, starting from 64.
21         int newcapacity = 64;
22         while (newcapacity < minnewcapacity) {
23             newcapacity <<= 1;
24         }
25 
26         return math.min(newcapacity, maxcapacity);
27     }

  1行:接受一个最小的新内存参数minnewcapacity。

  3行: 定义一个4mb的阈值常量threshold。

  5,6行: 如果minnewcapacity==threshold,那么新内存大小就是threshold。

  10-17行: 如果minnewcapacity>threshold, 新内存大小是min(maxcapacity, threshold * n)且threshold * n >= minnewcapacity。

  21-26行: 如果minnewcapacity<threshold, 新内存大小是min(maxcapacity, 64 * 2n)且64 * 2n >= minnewcapacity。

 

内存分配和释放的具体实现

  本章涉及到的内存分配和释放的具体实现只涉及到unpooled类型的bytebuf,即:

  unpooledheapbytebuf

  unpooleddirectbytebuf

  unpooledunsafeheapbytebuf

  unpooledunsafedirectbytebuf

  这几个具体实现中涉及到的内存分配和释放代码比较简洁,更容易明白bytebuf内存管理的原理。相比之下,pooled类型的bytebuf内存分配和释放的代码要复杂很多,会在后面的章节独立分析。

  

  unpooledheapbytebuf和unpooledunsafeheapbytebuf实现

   unpooledheapbytebuf中,内存分配的实现代码主要集中在capacity(int)和allocatearray()方法中。capacity分配新内存的步骤是

  • 调用allocatearray分配一块新内存。
  • 把旧内存中的实际复制到新内存中。
  • 使用新内存替换旧内存(24行)。
  • 释放掉旧内存(25行)。

   代码如下:

 1     @override
 2     public bytebuf capacity(int newcapacity) {
 3         checknewcapacity(newcapacity);
 4 
 5         int oldcapacity = array.length;
 6         byte[] oldarray = array;
 7         if (newcapacity > oldcapacity) {
 8             byte[] newarray = allocatearray(newcapacity);
 9             system.arraycopy(oldarray, 0, newarray, 0, oldarray.length);
10             setarray(newarray);
11             freearray(oldarray);
12         } else if (newcapacity < oldcapacity) {
13             byte[] newarray = allocatearray(newcapacity);
14             int readerindex = readerindex();
15             if (readerindex < newcapacity) {
16                 int writerindex = writerindex();
17                 if (writerindex > newcapacity) {
18                     writerindex(writerindex = newcapacity);
19                 }
20                 system.arraycopy(oldarray, readerindex, newarray, readerindex, writerindex - readerindex);
21             } else {
22                 setindex(newcapacity, newcapacity);
23             }
24             setarray(newarray);
25             freearray(oldarray);
26         }
27         return this;
28     }
29     

  capacity中复制旧内存数据到新内存中的时候分两种情况(newcapacity,oldcapacity分别是新旧内存的大小):

  • newcapacity>oldcapacity,这种情况比较简单,直接复制就好了(第8行)。不影响readerindex和writerindex。
  • newcapacity<oldcapacity,  这种情况比较复杂。capacity尽量把可读的数据复制新内存中。
    • 如果readerindex<newcapacity且writerindex<newcapacity。可读数据会全部转移到新内存中。readerindex和writerindex保持不变。
    • 如果readerindex<newcapacity且writeindex>newcapacity。可端数据会部分转移的新内存中,会丢失部分可读数据。readerindex不变,writerindex变成newcapacity。
    • 如果readerindex>newcapacity,数据全部丢失,readerindex和writerindex都会变成newcapacity。

   allocatearray方法负责分配一块新内存,它的实现是new byte[]。freearray方法负责释放内存,这个方法是个空方法。

  unpooledunsafeheapbytebuf是unloopedheadpbytebuf的直接子类,在内存管理上的差别是allocatearray的实现,unpooledunsafeheapbytebuf的实现是:  

1     @override
2     byte[] allocatearray(int initialcapacity) {
3         return platformdependent.allocateuninitializedarray(initialcapacity);
4     }

 

  unpooleddirectbytebuf和unpooledunsafedirectbytebuf实现

  unpooleddirectbytebuf类使用directbytebuffer作为内存,使用了directbytebuffer的能力来实现bytebuf接口。allocatedirect和freedirect方法负责分配和释放directbytebuffer。capacity(int)方法和unloopedheapbytebuf类似,使用allocatedirect创建一个新的directbytebuffer, 把旧内存数据复制到新内存中,然后使用新内存替换旧内存,最后调用freedirect方法释放掉旧的directbytebuffer。

 1     protected bytebuffer allocatedirect(int initialcapacity) {
 2         return bytebuffer.allocatedirect(initialcapacity);
 3     }
 4 
 5     protected void freedirect(bytebuffer buffer) {
 6         platformdependent.freedirectbuffer(buffer);
 7     }
 8 
 9   @override
10     public bytebuf capacity(int newcapacity) {
11         checknewcapacity(newcapacity);
12 
13         int readerindex = readerindex();
14         int writerindex = writerindex();
15 
16         int oldcapacity = capacity;
17         if (newcapacity > oldcapacity) {
18             bytebuffer oldbuffer = buffer;
19             bytebuffer newbuffer = allocatedirect(newcapacity);
20             oldbuffer.position(0).limit(oldbuffer.capacity());
21             newbuffer.position(0).limit(oldbuffer.capacity());
22             newbuffer.put(oldbuffer);
23             newbuffer.clear();
24             setbytebuffer(newbuffer);
25         } else if (newcapacity < oldcapacity) {
26             bytebuffer oldbuffer = buffer;
27             bytebuffer newbuffer = allocatedirect(newcapacity);
28             if (readerindex < newcapacity) {
29                 if (writerindex > newcapacity) {
30                     writerindex(writerindex = newcapacity);
31                 }
32                 oldbuffer.position(readerindex).limit(writerindex);
33                 newbuffer.position(readerindex).limit(writerindex);
34                 newbuffer.put(oldbuffer);
35                 newbuffer.clear();
36             } else {
37                 setindex(newcapacity, newcapacity);
38             }
39             setbytebuffer(newbuffer);
40         }
41         return this;
42     }

  对比unloopedheapbytebuf的capacity(int)方法,发现这两个实现非常类似,也分两种情况处理:

  • 18-24行,newcapacity > oldcapacity的情况。
  • 26-39行, newcapacity < oldcapacity的情况。

  两种情况对readerindex和writerindex的影响也一样,不同的是数据复制时的具体实现。  

  unpooledunsafedirectbytebuf和unpooleddirectbytebuf同属于abstractreferencecountedbytebuf的派生类,它们之间没有继承关系。但内存分配和释放实现是一样的,不同的地方是内存i/o。unpooledunsafedirectbytebuf使用unsafebytebufutil类之间读写directbytebuffer的内存,没有使用directbytebuffer的i/o能力。