关于Netty中的Recycler对象池

1、对象池的背景

    Netty本身作为通信用的框架,消息的创建和处理十分频繁,大多数的消息本身可循环使用的程度不高,大部分都是创建完传输后就被等待GC了。但是考虑到大部分消息只是消息部分内容有所差异,可以考虑对创建消息的对象进行循环利用,减少创建和GC带来的消耗。

2、Recycler图例

简单介绍以下,每个Recycler都会有将内部的Stack存储在InternalThreadLocalMap之中,并且将对象存储在Stack中,所以Stack是每个线程专属的,Recycler更可以说是一个用于访问Stack的窗口,类似ThreadLocal。Stack内部数据主要存储在DefaultHandle中,每个DefaultHandle负责存储一个对象。同时Stack的有一个叫head的WeakOrderQueue的属性,负责指向其他线程为该Stack创建的WeakOrderQueue,只要有其他线程为该Stack创建WeakOrderQueue,就会存储到前一个WeakOrderQueue的next域之中,以此形成一个由WeakOrderQueue组成的链。同样的,WeakOrderQueue本身也不存储数据,而是交给其内部类Link存储,Link内部也是由DefaultHandler组成的数组,并且通过next组成Link的单向链表。

对于每一个线程创建的对象,如果是其自身回收的,就放在Stack的DefaultHandle之中;如果是其他线程为其回收,则该线程会创建一个属于该Stack的WeakOrderQueue,并将回收的对象放入其中的Link,以及将WeakOrderQueue加入链路中。

对于Recycler源码的解析,要分4个部分说明

  • 本线程回收自己创建的对象
  • 本线程为其他线程回收对象
  • 从Stack中直接获取对象进行复用
  • 从WeakOrderQueue的Link中搬迁对象进行复用

3、本线程回收自己创建的对象

从Recycler中获取一个对象,直接调用其get方法即可,该方法返回前面定义的一个泛型T。

Recycler.get();

该get方法会尝试从线程本地变量获取Stack,再从中提取出一个DefaultHandle加以复用。

    public final T get() {
        /* 从Recycler中获取一个元素T */
        /* 如果maxCapacityPerThread值为0,那么每次都创建一个新的对象 */
        /* maxCapacity表示Stack能够存储的DefaultHandle最大数量*/
        if (maxCapacityPerThread == 0) {
            return newObject((Handle<T>) NOOP_HANDLE);
        }
        /*
         * fastThreadLocal的值获取是懒加载的,所以第一次会出初始化一个stack
         */
        Stack<T> stack = threadLocal.get();
        /* 从stakc中pop一个DefaultHandle */
        DefaultHandle<T> handle = stack.pop();
        if (handle == null) {
            handle = stack.newHandle();
            handle.value = newObject(handle);
        }
        return (T) handle.value;
    }

其中maxCapacityPerThread表示每个线程中Stack可以容纳的DefaultHandle的数量,因为在Stack中,DefaultHandle是以数组的形式存储的。

static final class Stack<T> {
    ...
    private DefaultHandle<?>[] elements;
    ...
}

所以必须在使用前指定该数组的初始化容量,该值如果不指定时默认为4096

public abstract class Recycler<T> {
    ...
    // 这里设定默认每个线程容量为4096
    private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024;
    int maxCapacityPerThread = SystemPropertyUtil.getInt("io.netty.recycler.maxCapacityPerThread",
                SystemPropertyUtil.getInt("io.netty.recycler.maxCapacity", DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD));
    if (maxCapacityPerThread < 0) {
        maxCapacityPerThread = DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD;
    }
    DEFAULT_MAX_CAPACITY_PER_THREAD = maxCapacityPerThread;
    
    ...
    // 每个线程默认容量
    private final int maxCapacityPerThread;
    private final int maxSharedCapacityFactor;
    private final int ratioMask;
    private final int maxDelayedQueuesPerThread;

    // Stack的获取采用懒加载的机制,所以会因第一次获取不到而尝试调用初始化方法
        private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
        // 在初始化的时候将maxCapacityPerThread注入到Stack中
        @Override
        protected Stack<T> initialValue() {
            return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
                    ratioMask, maxDelayedQueuesPerThread);
        }
    ...
    }
}

Mark: 可以通过设置参数-Dio.netty.recycler.maxCapacityPerThread 方式设置每个Stack的DefaultHandle的容量。

回到Recycler.get方法,可以发现在maxCapacityPerThread的值为0时,表示不会回收对象,每次都创建一个新的对象返回给调用方。

    public final T get() {
        /* 从Recycler中获取一个元素T */
        /* 如果maxCapacityPerThread值为0,那么每次都创建一个新的对象 */
        /* maxCapacity表示Stack能够存储的DefaultHandle最大数量*/
        if (maxCapacityPerThread == 0) {
            return newObject((Handle<T>) NOOP_HANDLE);
        }
        /*
         * fastThreadLocal的值获取是懒加载的,所以第一次会出初始化一个stack
         */
        Stack<T> stack = threadLocal.get();
        /* 从stakc中pop一个DefaultHandle */
        DefaultHandle<T> handle = stack.pop();
        if (handle == null) {
            handle = stack.newHandle();
            handle.value = newObject(handle);
        }
        return (T) handle.value;
    }

从本地变量获取到Stack,类似栈的方法调用pop方法返回一个DefaultHandle,如果该DefaultHandle为null,则创建一个新的DefaultHandle返回给调用方,注意因为是从Recycler中获取对象复用,所以不用重新放回到Stack中,Stack中的对象应该表示为回收后待复用的。在看pop方法前,我们先来看创建DefaultHandle的两个相关方法,Stack.newHandle()和newObject()。

    // DefaultHandle类定义
    static final class DefaultHandle<T> implements Handle<T> {
        private int lastRecycledId;
        private int recycleId;

        boolean hasBeenRecycled;

        private Stack<?> stack;
        private Object value;

        DefaultHandle(Stack<?> stack) {
            this.stack = stack;
        }

        @Override
        public void recycle(Object object) {
            if (object != value) {
                throw new IllegalArgumentException("object does not belong to handle");
            }

            Stack<?> stack = this.stack;
            // https://github.com/netty/netty/issues/8220
            if (lastRecycledId != recycleId || stack == null) {
                throw new IllegalStateException("recycled already");
            }

            stack.push(this);
        }
    }        
        
    // newHandle()方法定义
    DefaultHandle<T> newHandle() {
        return new DefaultHandle<T>(this);
    }

    // newObject()方法由继承了Recycler的类默认实现,下面已Entry类为例
    static final class Entry {
        private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
            @Override
            protected Entry newObject(Handle<Entry> handle) {
                return new Entry(handle);
            }
        };
        ...
    }

每个DefaultHandler有保留创建它的Stack的引用,所以可以从Stack找到DefaultHandle的同时,也可以从DefaultHandle找到Stack,这点在回收DefaultHandle的对象时十分方便。再者每个继承了Recycler的对象仅仅需要实现方法newObject用于没有对象可复用时创建新对象,内部逻辑通通在Recycler实现了。这两点在实现某个功能池的十分有利。

下面重点介绍Stack.pop方法,该方法作用主要时用于从Stack中返回对象,其中当DefaultHandle存在可复用对象时直接返回,以及没有可复用对象时从WeakOrderQueue搬迁对象过来。本节假设Stack无需从WeakOrderQueue获取对象时处理。

        DefaultHandle<T> pop() {
            // this.size是Stack的容量
            int size = this.size;
            // 如果当前stack没有存储任何的值
            if (size == 0) {
                // scavenge返回一个DefaultHandler
                if (!scavenge()) {
                    return null;
                }
                size = this.size;
                if (size <= 0) {
                    // double check, avoid races
                    return null;
                }
            }
            size --;
            DefaultHandle ret = elements[size];
            elements[size] = null;
            // ???这里不清楚
            if (ret.lastRecycledId != ret.recycleId) {
                throw new IllegalStateException("recycled multiple times");
            }
            ret.recycleId = 0;
            ret.lastRecycledId = 0;
            this.size = size;
            return ret;
        }

当Stack.DefaultHandle存在可复用的对象时,这个比较简单,直接根据当前DefaultHandle的最后一个对象返回复用,设置size--同时,将DefaultHandle[size]的位置设为null,避免重复占坑。对于lastRecycleId和recycleId两个值的理解,会在回收对象时给出。目前知道从Stack.pop()返回DefaultHandle时,会将该DefaultHandle这两个值同时设定为0。

4、线程从WeakOrderQueue中迁移对象进行回收

在pop的另一个分支中,如果当前Stack的size为0,就会尝试从其他地方获取对象进行回收。

现在我们还没看到线程是如何帮其他线程回收对象,所以先简单介绍下,线程会为预留一部分空间用于存储为其他线程回收的对象。这部分数据结构如下

    private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED =
            new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {
        @Override
        protected Map<Stack<?>, WeakOrderQueue> initialValue() {
            return new WeakHashMap<Stack<?>, WeakOrderQueue>();
        }
    };

每个线程会在自身本地变量(FastThreadLocal)这里存储一个Map<Stack<?>,WeakOrderQueue>类型,名为DELAYED_RECYCLED的对象,其中key为当前线程为了哪些线程回收了对象,就会把这些线程对应的Stack作为key放入到DELAYED_RECYCLED中(每个回收的DefaultHandle都有引用指向Stack,所以可以从这里获取到Stack的引用)。具体的WeakOrderQueue这用于存储回收的对象。WeakOrderQueue的数据结构如下

private static final class WeakOrderQueue {

        static final WeakOrderQueue DUMMY = new WeakOrderQueue();
        @SuppressWarnings("serial")
        static final class Link extends AtomicInteger {
            private final DefaultHandle<?>[] elements = new DefaultHandle[LINK_CAPACITY];

            private int readIndex;
            Link next;
        }

        static final class Head {
            private final AtomicInteger availableSharedCapacity;

            Link link;

            Head(AtomicInteger availableSharedCapacity) {
                this.availableSharedCapacity = availableSharedCapacity;
            }

            ...
        }

        private final Head head;
        private Link tail;
        // pointer to another queue of delayed items for the same stack
        private WeakOrderQueue next;
        private final WeakReference<Thread> owner;
        private final int id = ID_GENERATOR.getAndIncrement();

WeakOrderQueue内部有一个Link的对象,这里才是实际存储为其他线程回收对象的地方,而且每个Link有默认大小为LINK_CAPACITY,当一个Link不够存储时,交给下一个Link存储,Link由此组成单向链表。关于WeakOrderQueue空间内部大小的定义如下

        MAX_SHARED_CAPACITY_FACTOR = max(2,
                SystemPropertyUtil.getInt("io.netty.recycler.maxSharedCapacityFactor",
                        2));

        MAX_DELAYED_QUEUES_PER_THREAD = max(0,
                SystemPropertyUtil.getInt("io.netty.recycler.maxDelayedQueuesPerThread",
                        // We use the same value as default EventLoop number
                        NettyRuntime.availableProcessors() * 2));

        LINK_CAPACITY = safeFindNextPositivePowerOfTwo(
                max(SystemPropertyUtil.getInt("io.netty.recycler.linkCapacity", 16), 16));
  • MAX_SHARED_CAPACITY_FACTOR,表示为其他线程预留的空间(Link链表可以存储数据的总大小,注意不是单个Link)和自身回收对象(Stack可以存储数据大小)直接的比值,默认是1:2,则Link链表最终可以存储        4096 / 2 = 2048个对象,注意是为每个其他线程存储2048个对象;
  • MAX_DELAYED_QUEUES_PER_THREAD,表示每个线程可以在DELAYED_RECYCLED中存储多少个Stack,换句话说,每个线程只能为MAX_DELAYED_QUEUES_PER_THREAD个线程回收对象;
  • LINK_CAPACITY,表示每个Link能够存储的DefaultHandle的数量,默认为16个,超出这个范围将创建新的Link,所有Link加起来的数量不能超过MAX_SHARED_CAPACITY_FACTOR。

除此之外,WeakOrderQueue本身也是一个链表,内部存储了next指针用于存储下一个WeakOrderQueue的地址引用。

private static final class WeakOrderQueue {

    ...
    private WeakOrderQueue next;
    ...
}

而在Stack中会存储第一个为本线程回收对象时创建的WeakOrderQueue

static final class Stack<T> {
    ...
    private WeakOrderQueue cursor, prev;
    private volatile WeakOrderQueue head;
    ...
}

这样就可以在Stack中通过head域遍历其他线程为本线程回收的对象WeakOrderQueue。

回到pop方法中,会先获取当前Stack的size(int size = this.size;),如果这个size的值为0,则表示当前Stack的DefaultHandle没有对象可以复用,需要调用scavenge获取由其他线程为本线程回收的对象进行复用。

        boolean scavenge() {
            // continue an existing scavenge, if any
            if (scavengeSome()) {
                return true;
            }

            // reset our scavenge cursor
            prev = null;
            cursor = head;
            return false;
        }

        boolean scavengeSome() {
            WeakOrderQueue prev;
            // this.cursor一开始为null,表示上一次没有对象可回收时遍历在哪个WeakOrderQueue
            WeakOrderQueue cursor = this.cursor;
            // cursor为null,说明该线程是第一次遍历WeakOrderQueue
            if (cursor == null) {
                // prev 不是null就是cursor的上一个WeakOrderQueue
                prev = null;
                // head是Stack中存储的指向第一个WeakOrderQueue
                cursor = head;
                // 如果cursor为空,那么head就为空,相当于没有其他线程为当前线程收集了对象
                // Map<Stack<?>, WeakOrderQueue>中key值为当前线程的Stack的value不存在
                // 所以这里返回false
                if (cursor == null) {
                    return false;
                }
            } else {
                // 如果cursor不为空的情况,prev指向this.prev,也就是上一次遍历到哪个WeakOrderQueue的再上一个WeakOrderQueue
                prev = this.prev;
            }

            boolean success = false;
            // cursor表示上一次遍历的WeakOrderQueue的是哪一个
            do {
                // 假设第一次进来,所以cursor应该为head,所以相当于this.head.transfer
                // WeakOrderQueue.transfer(Stack)返回false表示失败
                if (cursor.transfer(this)) {
                    success = true;
                    break;
                }
                // 走到这里说明搬迁失败了,需要去下一个WeakOrderQueue获取
                WeakOrderQueue next = cursor.next;
                if (cursor.owner.get() == null) {
                    // If the thread associated with the queue is gone, unlink it, after
                    // performing a volatile read to confirm there is no data left to collect.
                    // We never unlink the first queue, as we don't want to synchronize on updating the head.

                    //如果与队列相关联的线程不见了,请在执行易失性读取以确认没有剩余数据要收集之后将其取消链接。
                    // 我们永远不会取消链接第一个队列,因为我们不想在更新头部时进行同步。

                    // WeakOrderQueue的线程并不是表示Stack的线程,而是帮忙回收Stack数据的线程,有可能线程已经结束了
                    // 导致上面的owner为null,毕竟是WeakReference
                    // cursor.hasFinalData() 是判断最后一个link的是否有数据
                    if (cursor.hasFinalData()) {
                        for (;;) {
                            if (cursor.transfer(this)) {
                                success = true;
                            } else {
                                break;
                            }
                        }
                    }

                    if (prev != null) {
                        prev.setNext(next);
                    }
                } else {
                    prev = cursor;
                }
                // 从下一个WeakOrderQueue开始重新遍历
                cursor = next;

            } while (cursor != null && !success);

            // prev表示cursor的上一个
            this.prev = prev;
            this.cursor = cursor;
            return success;
        }

        
        // 用于从WeakOrderQueue搬迁对象到Stack.DefaultHandle方法
        boolean transfer(Stack<?> dst) {
            // 获取第一个WeakOrderQueue的head.link的值,这里记位link-first
            Link head = this.head.link;
            // 如果一个link为空直接退出,去查找下一个线程为本线程创建的WeakOrderQueue
            if (head == null) {
                return false;
            }

            // 第一次进来readIndex应该为0,不能与link的最大容量LINK_CAPACITY
            if (head.readIndex == LINK_CAPACITY) {
                // 如果订阅LINK_CAPACITY,说明当前link已经检索玩没有可回收的对象了
                // 直接判断下一个是否为空,如果为空,返回null说明检索完了该WeakOrderQueue的值
                if (head.next == null) {
                    return false;
                }
                // 交给GC去回收当前link,将WeakOrder.head.link指向下一个link
                this.head.link = head = head.next;
                // 因为当前link已经没用并且交给GC去回收了,所以调用reclaimSpace修改可申请回收对象大小
                this.head.reclaimSpace(LINK_CAPACITY);
            }

            // 走到这一步,link-first要么的readIndex不等于LINK_CAPACITY,
            // 要么就是link-first等于LINK_CAPACITY,所以进入link-first.next,也就是下一个
            // 记为link-second,如果link-secnod不为null,那么必然有对象可以被回收利用
            // 所以readIndex表示上一次回收的游标+1,也就是这次回收开始的地方,当它等于LINK_CAPACITY
            // 时,就表示没有对象可以被回收,默认应该是从0开始,因为是一个数组
            final int srcStart = head.readIndex;
            // srcEnd暂时不太清除,猜测是当前link内部存储的DefaultHandler的当前数组size,因为link本身是继承
            // AtomicInteger的,所以只需要用内置的方法get和add原子存储
            int srcEnd = head.get();
            // 判断数组的容量大小,如果为0则没法回收返回false
            final int srcSize = srcEnd - srcStart;
            if (srcSize == 0) {
                return false;
            }

            // dst是stack当前数量大小
            final int dstSize = dst.size;
            // 计算当前stack的数量大小 + link的存储数量大小
            final int expectedCapacity = dstSize + srcSize;

            // java数组中,length表述数组可存储容量,size表示当前数组已经存储的数据
            // 这里表示当前stack的容量 + 即将从link转移过来的数量会不会超过当前stack的DefaultHandler可以
            // 容纳的数据量
            // 如果需要的数据容量,大于当前Stack的DefaultHandler当前数组的最大容量继续扩容
            if (expectedCapacity > dst.elements.length) {
                // 返回的是扩容后数组数组总大小(包括已经有数组的部分3)
                final int actualCapacity = dst.increaseCapacity(expectedCapacity);
                // 这段的意思是,actualCapacity 是扩容后数组数组总大小,减去dstSize 就是剩余可存储的容量
                // 加上stcStart就表示原来link中,从srcStart触发能挖走读少数据,并且取最小值
                // 这个srcEnd也不能操作原来head的结束位置,所以取两者的最小值。
                srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);
            }

            // 上面扩容好了数组,表示有位置存储了,就应该开始搬迁了
            if (srcStart != srcEnd) {
                // srcElems 要搬数据地方
                final DefaultHandle[] srcElems = head.elements;
                // dstElems 要放入数据的地方
                final DefaultHandle[] dstElems = dst.elements;
                // newDstSize = dstSize就是dstElems 当前数据数量
                int newDstSize = dstSize;
                for (int i = srcStart; i < srcEnd; i++) {
                    DefaultHandle element = srcElems[i];
                    /* 这段需要研究下 */
                    if (element.recycleId == 0) {
                        element.recycleId = element.lastRecycledId;
                    } else if (element.recycleId != element.lastRecycledId) {
                        throw new IllegalStateException("recycled already");
                    }
                    srcElems[i] = null;

                    if (dst.dropHandle(element)) {
                        // Drop the object.
                        continue;
                    }
                    /* 这段需要研究下 */
                    element.stack = dst;
                    dstElems[newDstSize ++] = element;
                }

                // 如果当前link的全部数据被回收了,那么就回收可用容量,并且link直接丢弃去gc
                if (srcEnd == LINK_CAPACITY && head.next != null) {
                    // Add capacity back as the Link is GCed.
                    this.head.reclaimSpace(LINK_CAPACITY);
                    this.head.link = head.next;
                }

                // 这里readIndex等于srcEnd,保留这一次回收的点,下一次从srcEnd开始回收
                head.readIndex = srcEnd;
                // 当前stack的大小等于原来大小,表示没搬迁成功,返回false
                if (dst.size == newDstSize) {
                    return false;
                }
                // 修改stack的DefaultHandler的大小
                dst.size = newDstSize;
                return true;
            } else {
                // The destination stack is full already.
                return false;
            }
        }

        // 因为DefaultHandler是一个数组不像list可以无限存储
        // 所以需要提前考虑数组的容量是否来的及存储
        int increaseCapacity(int expectedCapacity) {
            // 当前DefaultHandler的大小存储
            int newCapacity = elements.length;
            // 最大存储,应该是4k
            int maxCapacity = this.maxCapacity;
            do {
                // 扩容1倍
                newCapacity <<= 1;
                // 只要Stack的DefaultHandler小于目标的容量,而且必须在maxCapacity(4096内)
            } while (newCapacity < expectedCapacity && newCapacity < maxCapacity);

            // 判断新的容量大小是否超过最大限制程度
            newCapacity = min(newCapacity, maxCapacity);
            // 复制数组的数据
            if (newCapacity != elements.length) {
                elements = Arrays.copyOf(elements, newCapacity);
            }

            return newCapacity;
        }

5、本线程回收自身创建的对象

在讲解回收对象时,先来看看如何使用Recycler提供的对象回收Demo

import io.netty.util.Recycler;

class User {
    private String name;
    private Recycler.Handle<User> handle;

    public User(String name, Recycler.Handle<User> handle) {
        this.name = name;
        this.handle = handle;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Recycler.Handle<User> getHandle() {
        return handle;
    }

    public void setHandle(Recycler.Handle<User> handle) {
        this.handle = handle;
    }
}

public class UserRecycler extends Recycler<User> {
    @Override
    protected User newObject(Handle<User> handle) {
        return new User("theone", handle);
    }

    public static void main(String[] args) {
        UserRecycler recycler = new UserRecycler();
        User user = recycler.get();
    }
}

当你使用Recycler提供的功能去回收一个对象的时候,该对象需要保存容器DefaultHandle,以便在不需要时可以回收对象。例如

user.getHandle().recycle(user);

回收一个对象的方法放在DefaultHandle实现中,该方法为继承Handle接口所必须实现的

    public interface Handle<T> {
        void recycle(T object);
    }

    static final class DefaultHandle<T> implements Handle<T> {
        private int lastRecycledId;
        private int recycleId;

        boolean hasBeenRecycled;

        private Stack<?> stack;
        private Object value;

        DefaultHandle(Stack<?> stack) {
            this.stack = stack;
        }

        @Override
        public void recycle(Object object) {
            if (object != value) {
                throw new IllegalArgumentException("object does not belong to handle");
            }

            Stack<?> stack = this.stack;
            // https://github.com/netty/netty/issues/8220
            if (lastRecycledId != recycleId || stack == null) {
                throw new IllegalStateException("recycled already");
            }

            stack.push(this);
        }
    }

所以可以简单得到一个结论,DefaultHandle为实际对象的容器,负责保存对象以及回收对象,每个线程有一个Stack,用于保存回收的对象DefaultHandle数组。

从DefaultHandl.Recycle这个Handle的默认实现来看,其通过DefaultHandle获取Stack域,然后将自身塞回去Stack的DefaultHandle的数组中。

        void push(DefaultHandle<?> item) {
            Thread currentThread = Thread.currentThread();
            if (threadRef.get() == currentThread) {
                // The current Thread is the thread that belongs to the Stack, we can try to push the object now.
                pushNow(item);
            } else {
                // The current Thread is not the one that belongs to the Stack
                // (or the Thread that belonged to the Stack was collected already), we need to signal that the push
                // happens later.
                pushLater(item, currentThread);
            }
        }

Stack中会存储创建该Stack的线程引用threadRef,所以通过判断存储的线程引用和当前线程Thread.currentThread就可以判断当前是在给自己回收对象还是为其他线程回收对象,这里我们先看前者pushNow(),为自己回收线程。

        private void pushNow(DefaultHandle<?> item) {
            if ((item.recycleId | item.lastRecycledId) != 0) {
                throw new IllegalStateException("recycled already");
            }
            item.recycleId = item.lastRecycledId = OWN_THREAD_ID;

            int size = this.size;
            // dropHandle 为true表示需要抛弃这个对象,避免Stack每一个都回收导致爆炸性增长
            if (size >= maxCapacity || dropHandle(item)) {
                // Hit the maximum capacity or should drop - drop the possibly youngest object.
                return;
            }
            if (size == elements.length) {
                elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
            }

            elements[size] = item;
            this.size = size + 1;
        }

        //防止Stack的DefaultHandle[]数组发生爆炸性的增长,所以默认采取每8个元素回收一个,扔掉7个的策略),如果不需要回收,直接返回
        boolean dropHandle(DefaultHandle<?> handle) {
            if (!handle.hasBeenRecycled) {
                if ((++handleRecycleCount & ratioMask) != 0) {
                    // Drop the object.
                    return true;
                }
                // hasBeenRecycled 仅仅用于标记是否被回收
                handle.hasBeenRecycled = true;
            }
            return false;
        }

为自己回收线程比较简单,只要判断自身Stack的DefaultHandle数组是否还有空间回收即可,或者调用dropHandle()判断是否需要回收。具体流程如下

  • 获取当前Stack中DefaultHandle数组的大小,如果超过maxCapacity(默认为4096,也就是DEFAULT_MAX_CAPACITY_PER_THREAD),那么可以返回不会收该对象;
  • 调用dropHandle(),判断当前Stack回收的对象次数是否是ratioMask的整数倍,如果不是则不会受该对象,避免回收太快,导致Stack中DefaultHandle数组爆炸性增长;
  • 如果没有超过数组最大容量,dropHandle()判断也需要回收对象,那么判断当前数组是否可以存放该回收对象,否则则需要扩容数组,默认是扩容1倍(size << 1)。

6、本线程回收其他线程回收的对象

如果线程回收的对像发现是其他线程创建的,则需要考虑帮忙回收。这里我们来看pushLater的实现。

        private void pushLater(DefaultHandle<?> item, Thread thread) {
            // 从FastThreadLocal从获取到 Map<Stack<?>, WeakOrderQueue>
            Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
            WeakOrderQueue queue = delayedRecycled.get(this);
            // 如果queue为空,那么则当前线程中没有存储当前需要回收的线程的stack
            if (queue == null) {
                // 注意这里DELAYED_RECYCLED本身也是有最大值的,没法再帮忙插入更多线程的stakc,所以需要抛弃这个
                if (delayedRecycled.size() >= maxDelayedQueues) {
                    // Add a dummy queue so we know we should drop the object
                    delayedRecycled.put(this, WeakOrderQueue.DUMMY);
                    return;
                }
                // 分配一个队列
                // Check if we already reached the maximum number of delayed queues and if we can allocate at all.
                if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {
                    // drop object
                    return;
                }
                delayedRecycled.put(this, queue);
            } else if (queue == WeakOrderQueue.DUMMY) {
                // drop object
                return;
            }

            // DefaultHandler不是放在WeakOrderQueue中,而是WeakOrderQueue.Head.Link中,所以这里的add要进去看下
            queue.add(item);
        }

DELAYED_RECYCLED是一个FastThreadLocal类型,这种相当于是一个窗口用于获取存储在线程中的数据。在pushLaster方法中直接获取Map<Stack<?>, WeakOrderQueue>,该Map存储了当前线程帮其他线程回收的对象集合。这里当初把我看迷糊了一分钟是在于下一句delayedRecycled.get(this)。

前面说了DELAYED_RECYCLED是以Stack为key作为存储的Map,虽然Stack由每个线程创建,但是其他线程是可以访问到这个Stack的。就在这里回收的时候,如果发现当前回收的DefaultHandle对应的Stack不是当前回收线程创建的,那么就会触发去DELAYED_RECYCLED查找key = Stack的WeakOrderQueue,并尝试将其放入。

如果从Stack获取到的WeakOrderQueue为空,那么表示当前回收线程没有回收该创建Stack的线程的对象。所以需要判断是否由足够的空间用于回收。每个线程最多能够为maxDelayedQueue个Stack回收。

                if (delayedRecycled.size() >= maxDelayedQueues) {
                    // Add a dummy queue so we know we should drop the object
                    delayedRecycled.put(this, WeakOrderQueue.DUMMY);
                    return;
                }

这里如果判断超出了maxDelayedQueues的限制,那么就会插入一个WearOrderQueue.DUMMY,表示我们需要直接丢弃这个待回收的对象。相反,如果没超出则分配一个队列,并加入当前线程的Map<Stack<?>,WearOrderQueue>中。

                if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {
                    // drop object
                    return;
                }
                delayedRecycled.put(this, queue);

分配一个队列采用的是WeakOrderQueue的一个静态方法

        static WeakOrderQueue allocate(Stack<?> stack, Thread thread) {
            // We allocated a Link so reserve the space
            return Head.reserveSpace(stack.availableSharedCapacity, LINK_CAPACITY)
                    ? newQueue(stack, thread) : null;
        }

            // recerseSpace作用只是扣除空间的容量,没有创建队列
        static boolean reserveSpace(AtomicInteger availableSharedCapacity, int space) {
                assert space >= 0;
            for (;;) {
                // 获取当前线程为其他线程准备的可用回收空间大小,然后扣除space的大小
                int available = availableSharedCapacity.get();
                if (available < space) {
                    return false;
                }
                if (availableSharedCapacity.compareAndSet(available, available - space)) {
                    return true;
                }
            }
        }

        static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {
            final WeakOrderQueue queue = new WeakOrderQueue(stack, thread);
            // Done outside of the constructor to ensure WeakOrderQueue.this does not escape the constructor and so
            // may be accessed while its still constructed.
            stack.setHead(queue);

            return queue;
        }

        // Marked as synchronized to ensure this is serialized.
        synchronized void setHead(WeakOrderQueue queue) {
            queue.setNext(head);
            head = queue;
        }

       private void setNext(WeakOrderQueue next) {
            assert next != this;
            this.next = next;
        }

这里创建新队列的同时,会把刚创建的队列加入到带回收的DefaultHandle对应的Stack的head域中,目的是为了方便在复用对象的时候遍历。同时为了避免多个线程同时创建,用Synchronized修饰setHead()方法。

private volatile WeakOrderQueue head;

7、Recycler对象回收和创建流程图

8、关于在Stack中使用WeakReference<Thread>,而不是直接存储Thread

除了在Stack中使用弱引用之外,WeakOrderQueue中本身存储的是WeakReference。

// 在Stack中
final WeakReference<Thread> threadRef;

// 在WeakOrderQueue中
private final WeakReference<Thread> owner;

这里使用弱引用的原因在于,避免外部的线程被回收时,当前线程还保留着这个对象的引用。这不仅会导致外部线程没有被回收,而且也无法回收外部线程占用在Map<Stack<?>, WearOrderQueue>的一部分空间。所以这里采用弱引用,当外部线程被回收时,无论时Stack还是WeakOrderQueue中的引用都会为null,就可以通过这点判断是否可以丢弃这部分空间的占用。这点在复用对象时体现逻辑在Stack.scavengeSome()中

                WeakOrderQueue next = cursor.next;
                if (cursor.owner.get() == null) {
                    // If the thread associated with the queue is gone, unlink it, after
                    // performing a volatile read to confirm there is no data left to collect.
                    // We never unlink the first queue, as we don't want to synchronize on updating the head.
                    if (cursor.hasFinalData()) {
                        for (;;) {
                            if (cursor.transfer(this)) {
                                success = true;
                            } else {
                                break;
                            }
                        }
                    }

                    if (prev != null) {
                        prev.setNext(next);
                    }
                }

上面代码的逻辑是,判断当前遍历的WeakOrderQueue对应的Owner(线程)是否为null,是的话就尝试搬空内部的对象,然后在下面的if判断如果prev != null,就通过setNext方法丢弃该cursor。所以我们也知道了在Stack中存储prev的作用,它表示上一次遍历的cursor的前一个指针,当cursor为null,就可以通过访问prev引用丢弃这个cursor。

这里由一个小疑问? 如果当前线程要回收的Stack对应的线程被回收了,根据push的逻辑会走pushLater的分支,这会不会造成空间浪费?

        void push(DefaultHandle<?> item) {
            Thread currentThread = Thread.currentThread();
            // 如果Stack对应的线程被回收,这里threadRef.get()返回的是null,所以走pushLater分支
            if (threadRef.get() == currentThread) {
                // The current Thread is the thread that belongs to the Stack, we can try to push the object now.
                pushNow(item);
            } else {
                // The current Thread is not the one that belongs to the Stack
                // (or the Thread that belonged to the Stack was collected already), we need to signal that the push
                // happens later.
                pushLater(item, currentThread);
            }
        }

        // DefaultHandle是需要回收的,thread表示当前线程
        private void pushLater(DefaultHandle<?> item, Thread thread) {
            Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
            WeakOrderQueue queue = delayedRecycled.get(this);
            
            // 如果回收线程没有回收关于这个item的Stack,所以这里应该会出现queue为null
            // 当空间足够时,会为一个null的线程调用allocate的方法创建WeakOrderQueue
            if (queue == null) {
                if (delayedRecycled.size() >= maxDelayedQueues) {
                    // Add a dummy queue so we know we should drop the object
                    delayedRecycled.put(this, WeakOrderQueue.DUMMY);
                    return;
                }
                if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {
                    return;
                }
                delayedRecycled.put(this, queue);
            } else if (queue == WeakOrderQueue.DUMMY) {
                return;
            }

            queue.add(item);
        }

9、LastRecycleId和recycleId

这两个是出现在DefaultHandle的属性

    static final class DefaultHandle<T> implements Handle<T> {
        private int lastRecycledId;
        private int recycleId;
    
        ...
    }

初始化一个DefaultHandle时,这两个值为0

        DefaultHandle(Stack<?> stack) {
            this.stack = stack;
        }

在pushNow的时候,这两个值会被赋予同一个值

item.recycleId = item.lastRecycledId = OWN_THREAD_ID;

而在复用对象时候,在pop方法成功获取到一个DefaultHandle时,设置两个值同时0

        DefaultHandle<T> pop() {
            ...
            ret.recycleId = 0;
            ret.lastRecycledId = 0;
            ...
        }

而在其他线程帮忙回收时,在pushLater最后调用add方法设置为回收的WeakOrderQueue的id

        void add(DefaultHandle<?> handle) {
            // 将当前DefaultHandler最后一个回收的id设置为当前回收的WeakOrderQueue
            handle.lastRecycledId = id;
            ...
        }

看完赋值,我们再看使用场景,在DefaultHandle.recycler方法中会尝试判断lastRecycleId是否等于recycleId,不是则表示回收多次。

        if (lastRecycledId != recycleId || stack == null) {
            throw new IllegalStateException("recycled already");
        }

同样在transfer时候,也有过判断DefaultHandle是否被回收过

        else if (element.recycleId != element.lastRecycledId) {
            throw new IllegalStateException("recycled already");
        }

所以可以做个大胆的猜测,lastRecycleId和  recycleId用于判断这个DefaultHandle是否被回收多次,具体如下:

  • 当一个DefaultHandle被创建或者从对象池中返回(pop方法)时,这两个值就会为0;
  • 当DefaultHandle被内部Stack对应的线程,也就是创建这个DefaultHandle的线程回收时,这两个值也会保持一致,只不过是Recycle的一个域OWN_THREAD_ID (OWN_THREAD_ID = ID_GENERATOR.getAndIncrement());
  • 当DefaultHandle被外部线程回收时,只会把lastRecycledId设置为WeakOrderQueue对应的ID;
  • 所以可以看出,无论时被pushNow还是pushLater,始终都会让lastRecycledId和recycledId的值不会为0,这样就表示被回收过;
  • 而初始化或者从pop返回时,为0表示还未被回收;
  • 你会看到判断lastRecycledId != recycleId 的逻辑出现在DefaultHandle.recycle()、transfer以及在pushNow中,这是因为如果出现不一致,那么就是被外面的线程通过pushLater回收了,避免重复回收。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值