拍案叫绝!Jackson内存设计中的组合拳——TextBuffer和BufferRecycler

    技术2025-02-15  20

    Jackson作为Java三大Json框架之一,也是SpringBoot的默认序列化框架,具有速度快、内存占用低等特点。在rest服务盛行的今天,序列化和反序列化操作在系统中是一个极其常见的操作,这部分带来的内存开销也是一块重点。今天来看一下Jsckson中为了节省内存的神操作之一——TextBuffer和BufferRecycler的组合拳。

    TextBuffer

    TextBuffer是干嘛的?翻译官方注解,可以看作是一个StringBuffer(其实更应该是StringBuilder,因为它也是线程不安全的),但是它有一些骚操作。 StringBuffer在添加内容时,会对char[]进行容量判断,如果不足会进行扩容,并通过数组复制将原数据复制到新数组中。

    private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } }

    扩容复制会带来额外的性能开销。 TextBuffer就避免了这种复制,它是怎么做到的? TextBuffer使用了分段char[]的技术来缓冲内容,其内部持有一个列表private ArrayList<char[]> _segments;用来存放数组,当当前数组不够用了,新申请一个放后面的内容,然后加入到分段列表中。

    是不是很简单?!你咋没想到呢?

    你以为这就完了吗?还有另一个小伙伴BufferRecycler没登场呢!

    BufferRecycler

    从名字看出,这应该是一个用来循环利用buffer的一个类? 没错!TextBuffer在需要申请新的空间时,不是自己直接申请内存的,而是向BufferRecycler申请一个新的数组空间。当TextBuffer清空数据时,不会释放空间,而是将空间还给BufferRecycler,来重新利用数组空间。 BufferRecycler自身通过二维数组维护了多个数组的引用,当有其他对象来申请数组空间时,BufferRecycler会将自身持有的数组给对方,如果没有了则新建。当其他对象用完了数组后会将该数组空间的引用还给BufferRecycler,BufferRecycler将该引用记录到自己的二维数组中,以便重新分配。

    public char[] allocCharBuffer(int ix, int minSize) { final int DEF_SIZE = charBufferLength(ix); if (minSize < DEF_SIZE) { minSize = DEF_SIZE; } //从二维数组中拿到buffer引用 char[] buffer = _charBuffers[ix]; if (buffer == null || buffer.length < minSize) { //空则进行新分配 buffer = calloc(minSize); } else { //不为空自己不再持有该buffer引用,表示将其分配出去了 _charBuffers[ix] = null; } //分配buffer return buffer; } public void releaseCharBuffer(int ix, char[] buffer) { //将归还过来的buffer的引用记录到自身的二维数组中以便进行复用 _charBuffers[ix] = buffer; }

    很简单的设计理念,但简单中透漏着细节和优雅,拍案叫绝!

    Processed: 0.009, SQL: 9