netty源码分析 - Recycler 对象池的设计

  1. 《netty源码分析4 - Recycler对象池的设计》:https://www.jianshu.com/p/854b855bd198
  2. 《Netty 对象池实践优化》:https://blog.csdn.net/zhousenshan/article/details/82942381
  3. 《Netty轻量级对象池实现分析》:https://www.cnblogs.com/hzmark/p/netty-object-pool.html

一、为什么需要对象池

减少创建对象时内存分配的消耗,对象的内存分配机制见:https://www.cnblogs.com/java-zhao/p/5180492.html

对象进行重用,不会大量创建对象,减少GC压力

二、使用姿势

  1. private static final Recycler<User> userRecycler = new Recycler<User>() {
  2. @Override
  3. protected User newObject(Handle<User> handle) {
  4. return new User(handle);
  5. }
  6. };
  7. @Data
  8. static final class User {
  9. private String name;
  10. private Recycler.Handle<User> handle;
  11. public User(Recycler.Handle<User> handle) {
  12. this.handle = handle;
  13. }
  14. public void recycle() {
  15. handle.recycle(this);
  16. }
  17. }

首先创建一个对象池 Recycler 对象,重写了其 newObject(Handle handle)方法,当对象池中没有数据时,就调用该方法新建对象,在每一个新建的对象中,都会传入一个 Recycler.Handle对象,用于处理该对象的回收操作。

注意:

  • 一个 T 对象只有一个 Recycler.Handle 对象
  • 一个 Recycler.Handle 对象只属于一个 T 对象

2.1 同线程创建回收对象

  1. @Test
  2. public void testGetAndRecycleAtSameThread() {
  3. // 1、从回收池获取对象
  4. User user1 = userRecycler.get();
  5. // 2、设置对象并使用
  6. user1.setName("hello,java");
  7. System.out.println(user1);
  8. // 3、对象恢复出厂设置
  9. user1.setName(null);
  10. // 4、回收对象到对象池
  11. user1.recycle();
  12. // 5、从回收池获取对象
  13. User user2 = userRecycler.get();
  14. Assert.assertSame(user1, user2);
  15. }

步骤:

  1. main 线程先调用 userRecycler.get()从userRecycler 中获取 User 对象 user1,首次 userRecycler 中没有 User 对象,调用n ewObject 方法进行创建,之后返回;
  2. 对 user1 进行赋值并使用
  3. 使用结束之后,对 user1 恢复出厂设置(这一步需要使用者自己进行处理,Recycler 不会做)
  4. main 线程将 user1 回收到对象池
  5. main 线程从 userRecycler 中获取 User 对象 user2,这里的 user2 就是之前被回收的 user1 对象。

2.2 异线程创建回收对象

  1. @Test
  2. public void testGetAndRecycleAtDifferentThread() throws InterruptedException {
  3. // 1、从回收池获取对象
  4. User user1 = userRecycler.get();
  5. // 2、设置对象并使用
  6. user1.setName("hello,java");
  7. Thread thread = new Thread(()->{
  8. System.out.println(user1);
  9. // 3、对象恢复出厂设置
  10. user1.setName(null);
  11. // 4、回收对象到对象池
  12. user1.recycle();
  13. });
  14. thread.start();
  15. thread.join();
  16. // 5、从回收池获取对象
  17. User user2 = userRecycler.get();
  18. Assert.assertSame(user1, user2);
  19. }

步骤:

  • main 线程先调用 userRecycler.get() 从userRecycler 中获取 User 对象u ser1,首次 userRecycler 中没有User对象,调用 newObject 方法进行创建,之后返回;
  • 对 user1 进行赋值
  • 新线程 thread 使用 user1
  • 使用结束之后,对 user1 恢复出厂设置
  • thread 线程将 user1 回收到对象池
  • main 线程从 userRecycler 中获取 User 对象 user2,这里的 user2 就是之前被 thread 线程回收的 user1 对象。

最佳实践:在多个异线程进行 recycle() 方法时,要使用 try-catch,原因见 https://github.com/netty/netty/issues/8220 及其解决方案

三、数据结构

3.1 物理数据结构图

说明:

  1. 每一个 Recycler 对象包含一个 FastThreadLocal<Stack> threadLocal 实例;

    每一个线程包含一个 Stack 对象,该 Stack 对象包含一个 DefaultHandle[],而 DefaultHandle 中有一个属性 T value,用于存储真实对象。也就是说,每一个被回收的对象都会被包装成一个 DefaultHandle 对象(而该 DefaultHandle 对象实际上也作为真是对象的一个属性 -- 见“二、使用姿势”);

  2. Recyler 类包含一个类对象 FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED;

    每一个线程对象包含一个 Map<Stack<?>, WeakOrderQueue>,存储着为其他线程创建的 WeakOrderQueue 对象,WeakOrderQueue 对象中存储一个以 Head 为首的 Link 数组,每个 Link 对象中存储一个 DefaultHandle[] 数组,用于存放回收对象。

3.2 逻辑数据结构图(重要)

说明:(假设线程A创建的对象)

  1. 线程 A 回收 user1 时,直接将 user1 的 DefaultHandle 对象(内部包含 user1 对象)压入 Stack 的 DefaultHandle[] 中;

  2. 线程 B 回收 user1 时,会首先从其 Map<Stack<?>, WeakOrderQueue> 对象中获取 key=线程A的Stack 对象的 WeakOrderQueue,然后直接将 user1 的 DefaultHandle 对象(内部包含user1对象)压入该 WeakOrderQueue 中的 Link 链表中的尾部 Link 的 DefaultHandle[]中,同时,这个 WeakOrderQueue 会与线程 A 的 Stack 中的 head 属性进行关联,用于后续对象的 pop 操作;

  3. 当线程 A 从对象池获取对象时,如果线程 A 的 Stack 中有对象,则直接弹出;如果没有对象,则先从其 head 属性所指向的 WeakorderQueue 开始遍历 queue 链表,将 User 对象从其他线程的 WeakOrderQueue 中转移到线程 A 的 Stack 中(一次 pop 操作只转移一个包含了元素的 Link),再弹出。

注意:(假设线程A创建的对象)

  • Stack 中存储的是线程 A 回收的对象,以及从线程 X 的 WeakOrderQueue 中转移过来的对象。
  • WeakOrderQueue 中存储的是线程 X 回收的线程 A 创建的对象。

四、源码分析

4.1、Recycler 对象的创建

  1. /**
  2. * 唯一ID生成器,用在两处:
  3. * 1、当前线程ID
  4. * 2、WeakOrderQueue的id
  5. */
  6. private static final AtomicInteger ID_GENERATOR = new AtomicInteger(Integer.MIN_VALUE);
  7. /**
  8. * static变量, 生成并获取一个唯一id.
  9. * 用于pushNow()中的item.recycleId和item.lastRecycleId的设定
  10. */
  11. private static final int OWN_THREAD_ID = ID_GENERATOR.getAndIncrement();
  12. /**
  13. * 表示一个不需要回收的包装对象,用于在禁止使用Recycler功能时进行占位的功能
  14. * 仅当io.netty.recycler.maxCapacityPerThread<=0时用到
  15. */
  16. private static final Handle NOOP_HANDLE = new Handle() {
  17. @Override
  18. public void recycle(Object object) {
  19. // NOOP
  20. }
  21. };
  22. /**
  23. * 每个Stack默认的最大容量
  24. * 注意:
  25. * 1、当io.netty.recycler.maxCapacityPerThread<=0时,禁用回收功能(在netty中,只有=0可以禁用,<0默认使用4k)
  26. * 2、Recycler中有且只有两个地方存储DefaultHandle对象(Stack和Link),
  27. * 最多可存储MAX_CAPACITY_PER_THREAD + 最大可共享容量 = 4k + 4k/2 = 6k
  28. *
  29. * 实际上,在netty中,Recycler提供了两种设置属性的方式
  30. * 第一种:-Dio.netty.recycler.ratio等jvm启动参数方式
  31. * 第二种:Recycler(int maxCapacityPerThread)构造器传入方式
  32. */
  33. private static final int MAX_CAPACITY_PER_THREAD = Math.max(Integer.parseInt(System.getProperty("io.netty.recycler.maxCapacityPerThread", String.valueOf(4 * 1024))), 0);
  34. /**
  35. * 每个Stack默认的初始容量,默认为256
  36. * 后续根据需要进行扩容,直到<=MAX_CAPACITY_PER_THREAD
  37. */
  38. private static final int INITIAL_CAPACITY = Math.min(256, MAX_CAPACITY_PER_THREAD);
  39. /**
  40. * 最大可共享的容量因子。
  41. * 最大可共享的容量 = maxCapacity / maxSharedCapacityFactor,maxSharedCapacityFactor默认为2
  42. */
  43. private static final int MAX_SHARED_CAPACITY_FACTOR = Math.max(2, Integer.valueOf(System.getProperty("io.netty.recycler.maxSharedCapacityFactor", String.valueOf(2))));
  44. /**
  45. * 每个线程可拥有多少个WeakOrderQueue,默认为2*cpu核数
  46. * 实际上就是当前线程的Map<Stack<?>, WeakOrderQueue>的size最大值
  47. */
  48. private static final int MAX_DELAYE_DQUEUES_PERTHREAD = Math.max(0, Integer.parseInt(System.getProperty("io.netty.recycler.maxDelayedQueuesPerThread", String.valueOf(2 * Runtime.getRuntime().availableProcessors()))));
  49. /**
  50. * WeakOrderQueue中的Link中的数组DefaultHandle<?>[] elements容量,默认为16,
  51. * 当一个Link中的DefaultHandle元素达到16个时,会新创建一个Link进行存储,这些Link组成链表,当然
  52. * 所有的Link加起来的容量要<=最大可共享容量。
  53. */
  54. private static final int LINK_CAPACITY = MathUtil.safeFindNextPositivePowerOfTwo(Integer.parseInt(System.getProperty("io.netty.recycler.linkCapacity", String.valueOf(16))));
  55. /**
  56. * 回收因子,默认为8。
  57. * 即默认每8个对象,允许回收一次,直接扔掉7个,可以让recycler的容量缓慢的增大,避免爆发式的请求
  58. */
  59. private static final int RATIO = MathUtil.safeFindNextPositivePowerOfTwo(Integer.parseInt(System.getProperty("io.netty.recycler.ratio", String.valueOf(8))));
  60. /**
  61. * 1、每个Recycler类(而不是每一个Recycler对象)都有一个DELAYED_RECYCLED
  62. * 原因:可以根据一个Stack<T>对象唯一的找到一个WeakOrderQueue对象,所以此处不需要每个对象建立一个DELAYED_RECYCLED
  63. * 2、由于DELAYED_RECYCLED是一个类变量,所以需要包容多个T,此处泛型需要使用?
  64. * 3、WeakHashMap:当Stack没有强引用可达时,整个Entry{Stack<?>, WeakOrderQueue}都会加入相应的弱引用队列等待回收
  65. */
  66. private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED = new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {
  67. @Override
  68. protected Map<Stack<?>, WeakOrderQueue> initialValue() throws Exception {
  69. return new WeakHashMap<>();
  70. }
  71. };
  72. /**
  73. * 1、每个Recycler对象都有一个threadLocal
  74. * 原因:因为一个Stack要指明存储的对象泛型T,而不同的Recycler<T>对象的T可能不同,所以此处的FastThreadLocal是对象级别
  75. * 2、每条线程都有一个Stack<T>对象
  76. */
  77. private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
  78. @Override
  79. protected Stack<T> initialValue() throws Exception {
  80. return new Stack<>(Thread.currentThread(), MAX_CAPACITY_PER_THREAD, MAX_DELAYE_DQUEUES_PERTHREAD, MAX_SHARED_CAPACITY_FACTOR, MathUtil.safeFindNextPositivePowerOfTwo(RATIO) - 1);
  81. }
  82. @Override
  83. protected void onRemoved(Stack<T> stack) throws Exception {
  84. // Let us remove the WeakOrderQueue from the WeakHashMap directly if its safe to remove some overhead
  85. if (stack.threadRef.get() == Thread.currentThread() && DELAYED_RECYCLED.isSet()) {
  86. DELAYED_RECYCLED.get().remove(stack);
  87. }
  88. }
  89. };
  90. /**
  91. * 创建一个对象
  92. * 1、由子类进行复写,所以使用protected修饰
  93. * 2、传入Handle对象,对创建出来的对象进行回收操作
  94. */
  95. protected abstract T newObject(Handle<T> handle);
  96. /**
  97. * 提供对象的回收功能,由子类进行复写
  98. * 目前该接口只有两个实现:NOOP_HANDLE和DefaultHandle
  99. */
  100. public interface Handle<T> {
  101. void recycle(T object);
  102. }

注意:

在 netty 中,实际上提供了两种设置参数的方式,一种是通过 -Dio.netty.recycler.ratio 等 jvm 启动参数方式,另外一种就是提供了几个 Recycler 的构造函数,通过 Recycler(int maxCapacityPerThread) 构造器传入方式。

4.2、同线程获取对象

获取对象的操作起点是在 Recycler#get():

  1. /**
  2. * 获取对象
  3. */
  4. public final T get() {
  5. /**
  6. * 0、如果maxCapacityPerThread == 0,禁止回收功能
  7. * 创建一个对象,其Recycler.Handle<User> handle属性为NOOP_HANDLE,
  8. * 该对象的recycle(Object object)不做任何事情,即不做回收
  9. */
  10. if (MAX_CAPACITY_PER_THREAD == 0) {
  11. return newObject((Handle<T>) NOOP_HANDLE);
  12. }
  13. /**
  14. * 1、获取当前线程的Stack<T>对象
  15. */
  16. Stack<T> stack = threadLocal.get();
  17. /**
  18. * 2、从Stack<T>对象中获取DefaultHandle<T>
  19. */
  20. DefaultHandle<T> handle = stack.pop();
  21. if (handle == null) {
  22. /**
  23. * 3、 新建一个DefaultHandle对象 -> 然后新建T对象 -> 存储到DefaultHandle对象
  24. * 此处会发现一个DefaultHandle对象对应一个Object对象,二者相互包含。
  25. */
  26. handle = stack.newHandle();
  27. handle.value = newObject(handle);
  28. }
  29. /**
  30. * 4、返回value
  31. */
  32. return handle.value;
  33. }

当首次执行 threadLocal.get() 时,会调用 threadLocal#initialValue() 来创建一个 Stack 对象。

  1. /**
  2. * 该Stack所属的线程
  3. * why WeakReference?
  4. * 假设该线程对象在外界已经没有强引用了,那么实际上该线程对象就可以被回收了。
  5. * 但是如果此处用的是强引用,那么虽然外界不再对该线程有强引用,但是该stack对象还持有强引用
  6. * (假设用户存储了DefaultHandle对象,然后一直不释放,而DefaultHandle对象又持有stack引用),导致该线程对象无法释放。
  7. *
  8. * from netty:
  9. * The biggest issue is if we do not use a WeakReference the Thread may not be able to be collected at all
  10. * if the user will store a reference to the DefaultHandle somewhere and never clear this reference (or not clear it in a timely manner)
  11. */
  12. private WeakReference<Thread> threadRef;
  13. /**
  14. * Stack底层数据结构,真正的用来存储数据
  15. */
  16. private DefaultHandle<T>[] elements;
  17. /**
  18. * elements中的元素个数,同时也可作为操作数组的下标
  19. * 数组只有elements.length来计算数组容量的函数,没有计算当前数组中的元素个数的函数,所以需要我们去记录,不然需要每次都去计算
  20. */
  21. private int size;
  22. /**
  23. * elements最大的容量:默认最大为4k,4096
  24. */
  25. private int maxCapacity;
  26. /**
  27. * 可用的共享内存大小,默认为maxCapacity/maxSharedCapacityFactor = 4k/2 = 2k = 2048
  28. * 假设当前的Stack是线程A的,则其他线程B~X等去回收线程A创建的对象时,可回收最多A创建的多少个对象
  29. * 注意:那么实际上线程A创建的对象最终最多可以被回收maxCapacity + availableSharedCapacity个,默认为6k个
  30. *
  31. * why AtomicInteger?
  32. * 当线程B和线程C同时创建线程A的WeakOrderQueue的时候,会同时分配内存,需要同时操作availableSharedCapacity
  33. * 具体见:WeakOrderQueue.allocate
  34. */
  35. private AtomicInteger availableSharedCapacity;
  36. /**
  37. * DELAYED_RECYCLED中最多可存储的{Stack,WeakOrderQueue}键值对个数
  38. */
  39. private int maxDelayedQueues;
  40. /**
  41. * 默认为8-1=7,即2^3-1,控制每8个元素只有一个可以被recycle,其余7个被扔掉
  42. */
  43. private int ratioMask;
  44. public Stack(Thread threadRef,
  45. int maxCapacity,
  46. int maxDelayedQueues,
  47. int maxSharedCapacityFactor,
  48. int ratioMask) {
  49. this.elements = new DefaultHandle[Math.min(maxCapacity, INITIAL_CAPACITY)];
  50. this.threadRef = new WeakReference<>(threadRef);
  51. this.maxCapacity = maxCapacity;
  52. this.maxDelayedQueues = maxDelayedQueues;
  53. this.availableSharedCapacity = new AtomicInteger(Math.max(maxCapacity / maxSharedCapacityFactor, LINK_CAPACITY));
  54. this.ratioMask = ratioMask;
  55. }

创建好一个 Stack 对象之后,就会调用 stack.pop() 进行对象的获取。

  1. DefaultHandle<T> pop() {
  2. int size = this.size; // 将对象变量赋值给方法内变量,避免频繁从主内存中读取size变量吗??
  3. // 1. size=0 则说明本线程的Stack没有可用的对象,先从其它线程中获取。
  4. // 由于在transfer(Stack<?> dst)的过程中,可能将其他线程的WeakOrderQueue中的DefaultHandle对象
  5. // 传递到当前的Stack,所以size发生了变化,需要重新赋值
  6. if (size == 0) {
  7. if (!scavenge()) {
  8. return null;
  9. }
  10. size = this.size;
  11. }
  12. // 2. 注意:因为一个Recycler<T>只能回收一种类型T的对象,所以element可以直接使用操作size来作为下标来进行获取
  13. // 同时清空ret在Stack队列中的资源
  14. size --;
  15. DefaultHandle ret = elements[size];
  16. elements[size] = null;
  17. if (ret.lastRecycledId != ret.recycleId) {
  18. throw new IllegalStateException("recycled multiple times");
  19. }
  20. ret.recycleId = ret.lastRecycledId = 0;
  21. this.size = size;
  22. return ret;
  23. }

获取对象的逻辑也比较简单,当 Stack 中的 DefaultHandle[] 的 size 为 0 时,需要从其他线程的 WeakOrderQueue 中转移数据到 Stack 中的 DefaultHandle[],即 scavenge() 方法,该转移方式在 4.5 中再聊。当 Stack 中的 DefaultHandle[] 中最终有了数据时,直接获取最后一个元素,并进行一些防护性检查和元素的设置等。

假设最终确实无法从对象池中获取到对象,则会首先创建一个 DefaultHandle 对象,之后调用 Recycler 的子类重写的 newObject 方法进行 DefaultHandle 对象和真实对象 user1 的绑定,最后返回 user1。

  1. /***************************Stack****************************/
  2. DefaultHandle<T> newHandle() {
  3. return new DefaultHandle<>(this);
  4. }
  5. /**********************DefaultHandle**********************/
  6. static final class DefaultHandle<T> implements Handle<T> {
  7. /**
  8. * 真正的对象,value与Handle一一对应
  9. */
  10. private T value;
  11. /**
  12. * 标记是否已经被回收:
  13. * 该值仅仅用于控制是否执行 (++handleRecycleCount & ratioMask) != 0 这段逻辑,而不会用于阻止重复回收的操作,
  14. * 重复回收的操作由item.recycleId | item.lastRecycledId来阻止
  15. */
  16. private boolean hasBeenRecycled;
  17. /**
  18. * 只有在pushNow()中会设置值OWN_THREAD_ID
  19. * 在poll()中置位0
  20. */
  21. private int recycledId;
  22. /**
  23. * pushNow() = OWN_THREAD_ID
  24. * 在pushLater中的add(DefaultHandle handle)操作中 == id(当前的WeakOrderQueue的唯一ID)
  25. * 在poll()中置位0
  26. */
  27. private int lastRecycledId;
  28. /**
  29. * 当前的DefaultHandle对象所属的Stack
  30. */
  31. private Stack<T> stack;
  32. DefaultHandle(Stack<T> stack) {
  33. this.stack = stack;
  34. }
  35. @Override
  36. public void recycle(T object) {
  37. // 防护性判断
  38. if (object != value) {
  39. throw new IllegalArgumentException("object does not belong to handle");
  40. }
  41. Stack<T> stack = this.stack;
  42. if (lastRecycledId != recycledId || stack == null) {
  43. throw new IllegalStateException("recycled already");
  44. }
  45. /**
  46. * 回收对象,this指的是当前的DefaultHandle对象
  47. */
  48. stack.push(this);
  49. }
  50. }

Recycler#get 总体步骤:

  1. 如果 maxCapacityPerThread == 0,则禁用回收功能:新建 User 对象,将一个什么都不做的 NOOP_HANDLE 塞入 User 对象,进而不做回收操作;否则

  2. 获取当前线程的 Stack对象,如果没有,则新建一个 Stack 对象,在 Stack 对象的构造器中,会新建一个 DefaultHandle[],默认大小为 4k;

  3. 从 stack 对象中 pop 出一个 DefaultHandle 来,如果不为 null,直接返回 DefaultHandle 存储的真实对象 value;如果为 null,则新建一个 DefaultHandle 对象,之后新建 User 对象,并进行 DefaultHandle 对象和 User 对象的绑定,最终返回 user 对象。

Stack#pop的步骤:

  1. 首先获取当前的 Stack 中的 DefaultHandle 对象中的元素个数。

  2. 如果不为 0,直接获取 Stack 对象中 DefaultHandle[] 的最后一位元素,然后将该元素置为 null,之后做防护性检测,最后重置当前的 stack 对象的 size 属性以及获取到的 DefaultHandle 对象的 recycledId 和 lastRecycledId 回收标记,返回 DefaultHandle 对象。

  3. 如果为 0,则从其他线程的与当前的 Stack 对象关联的 WeakOrderQueue 中获取元素,并转移到 Stack 的 DefaultHandle[] 中(每一次 pop 只转移一个 Link),如果转移不成功,说明没有元素可用,直接返回 null;如果转移成功,则重置 size 属性为转移后的 Stack 的 DefaultHandle[] 的 size,之后按照第二步执行。

4.3 同线程回收对象

  1. /***********************DefaultHandle************************/
  2. @Override
  3. public void recycle(T object) {
  4. // 防护性判断
  5. if (object != value) {
  6. throw new IllegalArgumentException("object does not belong to handle");
  7. }
  8. // https://github.com/netty/netty/issues/8220
  9. if (this.lastRecycledId != this.recycledId) {
  10. throw new IllegalStateException("recycled already");
  11. }
  12. /**
  13. * 回收对象,this指的是当前的DefaultHandle对象
  14. */
  15. stack.push(this);
  16. }
  17. /***********************Stack************************/
  18. /**
  19. * 每有一个元素将要被回收, 则该值+1,例如第一个被回收的元素的handleRecycleCount=handleRecycleCount+1=0
  20. * 与ratioMask配合,用来决定当前的元素是被回收还是被drop。
  21. * 例如 ++handleRecycleCount & ratioMask(7),其实相当于 ++handleRecycleCount % 8,
  22. * 则当 ++handleRecycleCount = 0/8/16/...时,元素被回收,其余的元素直接被drop
  23. */
  24. private int handleRecycleCount = -1;
  25. void push(DefaultHandle<T> item) {
  26. Thread currentThread = Thread.currentThread();
  27. if (threadRef.get() == currentThread) {
  28. pushNow(item);
  29. } else {
  30. pushLater(item, currentThread);
  31. }
  32. }
  33. /**
  34. * 立刻将item元素压入Stack中
  35. */
  36. private void pushNow(DefaultHandle<T> item) {
  37. // (item.recycleId | item.lastRecycleId) != 0 等价于 item.recycleId!=0 && item.lastRecycleId!=0
  38. // 当item开始创建时item.recycleId==0 && item.lastRecycleId==0
  39. // 当item被recycle时,item.recycleId==x,item.lastRecycleId==y 进行赋值
  40. // 当item被poll之后, item.recycleId = item.lastRecycleId = 0
  41. // 所以当item.recycleId 和 item.lastRecycleId 任何一个不为0,则表示回收过
  42. if ((item.recycledId | item.lastRecycledId) != 0) {
  43. throw new IllegalStateException("recycled already");
  44. }
  45. item.recycledId = item.lastRecycledId = OWN_THREAD_ID;
  46. int size = this.size;
  47. if (size >= maxCapacity || dropHandle(item)) {
  48. return;
  49. }
  50. // stack中的elements扩容两倍,复制元素,将新数组赋值给stack.elements
  51. if (size == elements.length) {
  52. elements = Arrays.copyOf(elements, Math.min(size << 1, maxCapacity));
  53. }
  54. // 放置元素
  55. elements[size] = item;
  56. this.size = size + 1;
  57. }
  58. /**
  59. * 两个drop的时机
  60. * 1、pushNow:当前线程将数据push到Stack中
  61. * 2、transfer:将其他线程的WeakOrderQueue中的数据转移到当前的Stack中
  62. */
  63. private boolean dropHandle(DefaultHandle<T> item) {
  64. if (!item.hasBeenRecycled) {
  65. // 每8个对象:扔掉7个,回收一个
  66. // 回收的索引:handleRecycleCount - 0/8/16/24/32/...
  67. if ((++handleRecycleCount & ratioMask) != 0) {
  68. return true;
  69. }
  70. // 设置已经被回收了的标志,实际上此处还没有被回收,在pushNow(DefaultHandle<T> item)接下来的逻辑就会进行回收
  71. // 对于pushNow(DefaultHandle<T> item):该值仅仅用于控制是否执行 (++handleRecycleCount & ratioMask) != 0 这段逻辑,而不会用于阻止重复回收的操作,重复回收的操作由item.recycleId | item.lastRecycledId来阻止
  72. item.hasBeenRecycled = true;
  73. }
  74. return false;
  75. }

DefaultHandle#recycle 步骤:

  1. 首先 Recycler 对象做防护性检测,并且做多次回收的检测(netty-4.1.28 是没有这个检测的,笔者提了一个 bug 给 netty);

    之后向 stack 对象中 push 当前的 DefaultHandle 对象

  2. stack 先检测当前的线程是否是创建 stack 的线程,如果不是,则走异线程回收逻辑;如果是,则首先判断是否重复回收,然后判断 stack 的 DefaultHandle[] 中的元素个数是否已经超过最大容量(4k),如果是,直接返回;如果不是,则计算当前元素是否需要回收(netty 为了防止 Stack 的 DefaultHandle[] 数组发生爆炸性的增长,所以默认采取每 8 个元素回收一个,扔掉 7 个的策略),如果不需要回收,直接返回;如果需要回收,则

  3. 判断当前的 DefaultHandle[] 是否还有空位,如果没有,以 maxCapacity 为最大边界扩容 2 倍,之后拷贝旧数组的元素到新数组,然后将当前的 DefaultHandle 对象放置到 DefaultHandle[] 中

  4. 最后重置 stack.size 属性

4.4 异线程回收对象

  1. /***********************Stack************************/
  2. /**
  3. * 该值是当线程B回收线程A创建的对象时,线程B会为线程A的Stack对象创建一个WeakOrderQueue对象,
  4. * 该WeakOrderQueue指向这里的head,用于后续线程A对对象的查找操作
  5. * Q: why volatile?
  6. * A: 假设线程A正要读取对象X,此时需要从其他线程的WeakOrderQueue中读取,假设此时线程B正好创建Queue,并向Queue中放入一个对象X;假设恰好次Queue就是线程A的Stack的head
  7. * 使用volatile可以立即读取到该queue。
  8. *
  9. * 对于head的设置,具有同步问题。具体见此处的volatile和synchronized void setHead(WeakOrderQueue queue)
  10. */
  11. private volatile WeakOrderQueue head;
  12. /**
  13. * 先将item元素加入WeakOrderQueue,后续再从WeakOrderQueue中将元素压入Stack中
  14. */
  15. private void pushLater(DefaultHandle<T> item, Thread currentThread) {
  16. Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
  17. WeakOrderQueue queue = delayedRecycled.get(this);
  18. if (queue == null) {
  19. // 如果DELAYED_RECYCLED中的key-value对已经达到了maxDelayedQueues,则后续的无法回收 - 内存保护
  20. if (delayedRecycled.size() >= maxDelayedQueues) {
  21. delayedRecycled.put(this, WeakOrderQueue.DUMMY);
  22. return;
  23. }
  24. if ((queue = WeakOrderQueue.allocate(this, currentThread)) == null) {
  25. // drop object
  26. return;
  27. }
  28. delayedRecycled.put(this, queue);
  29. } else if (queue == WeakOrderQueue.DUMMY) {
  30. // drop object
  31. return;
  32. }
  33. queue.add(item);
  34. }
  35. /**
  36. * 假设线程B和线程C同时回收线程A的对象时,有可能会同时newQueue,就可能同时setHead,所以这里需要加锁
  37. * 以head==null的时候为例,
  38. * 加锁:
  39. * 线程B先执行,则head = 线程B的queue;之后线程C执行,此时将当前的head也就是线程B的queue作为线程C的queue的next,组成链表,之后设置head为线程C的queue
  40. * 不加锁:
  41. * 线程B先执行queue.setNext(head);此时线程B的queue.next=null->线程C执行queue.setNext(head);线程C的queue.next=null
  42. * -> 线程B执行head = queue;设置head为线程B的queue -> 线程C执行head = queue;设置head为线程C的queue
  43. *
  44. * 注意:此时线程B和线程C的queue没有连起来,则之后的poll()就不会从B进行查询。(B就是资源泄露)
  45. */
  46. synchronized void setHead(WeakOrderQueue queue) {
  47. queue.setNext(head);
  48. head = queue;
  49. }
  50. /***********************WeakOrderQueue************************/
  51. /**
  52. * 如果DELAYED_RECYCLED中的key-value对已经达到了maxDelayedQueues,
  53. * 对于后续的Stack,其对应的WeakOrderQueue设置为DUMMY,
  54. * 后续如果检测到DELAYED_RECYCLED中对应的Stack的value是WeakOrderQueue.DUMMY时,直接返回,不做存储操作
  55. */
  56. static final WeakOrderQueue DUMMY = new WeakOrderQueue();
  57. /**
  58. * WeakOrderQueue的唯一标记
  59. */
  60. private final int id = ID_GENERATOR.incrementAndGet();
  61. private final Head head;
  62. private Link tail;
  63. /**
  64. * pointer to another queue of delayed items for the same stack
  65. */
  66. private WeakOrderQueue next;
  67. /**
  68. * 1、why WeakReference?与Stack相同。
  69. * 2、作用是在poll的时候,如果owner不存在了,则需要将该线程所包含的WeakOrderQueue的元素释放,然后从链表中删除该Queue。
  70. */
  71. private final WeakReference<Thread> owner;
  72. private <T> WeakOrderQueue(Stack<T> stack, Thread currentThread) {
  73. // 创建有效Link节点,恰好是尾节点
  74. tail = new Link();
  75. // 创建Link链表头节点,只是占位符
  76. head = new Head(stack.availableSharedCapacity);
  77. head.link = tail;
  78. owner = new WeakReference<>(currentThread);
  79. }
  80. private WeakOrderQueue() {
  81. owner = null;
  82. head = new Head(null);
  83. }
  84. private static <T> WeakOrderQueue allocate(Stack<T> stack, Thread currentThread) {
  85. return Head.reserveSpace(stack.availableSharedCapacity, LINK_CAPACITY) ? newQueue(stack, currentThread) : null;
  86. }
  87. private static <T> WeakOrderQueue newQueue(Stack<T> stack, Thread currentThread) {
  88. // 创建WeakOrderQueue
  89. WeakOrderQueue queue = new WeakOrderQueue(stack, currentThread);
  90. // 将该queue赋值给stack的head属性
  91. stack.setHead(queue);
  92. /**
  93. * 将新建的queue添加到Cleaner中,当queue不可达时,
  94. * 调用head中的run()方法回收内存availableSharedCapacity,否则该值将不会增加,影响后续的Link的创建
  95. */
  96. return queue;
  97. }
  98. private void setNext(WeakOrderQueue next) {
  99. assert next != this;
  100. this.next = next;
  101. }
  102. private <T> void add(DefaultHandle<T> item) {
  103. item.lastRecycledId = id;
  104. Link tail = this.tail;
  105. int writeIndex;
  106. // 判断一个Link对象是否已经满了:
  107. // 如果没满,直接添加;
  108. // 如果已经满了,创建一个新的Link对象,之后重组Link链表,然后添加元素的末尾的Link(除了这个Link,前边的Link全部已经满了)
  109. if ((writeIndex = tail.get()) == LINK_CAPACITY) {
  110. if (!head.reserveSpace(LINK_CAPACITY)) {
  111. // drop item
  112. return;
  113. }
  114. /**
  115. * 此处创建一个Link,会将该Link作为新的tail-Link,之前的tail-Link已经满了,成为正常的Link了。重组Link链表
  116. * 之前是HEAD -> tail-Link,重组后HEAD -> 之前的tail-Link -> 新的tail-Link
  117. */
  118. tail = this.tail = tail.next = new Link();
  119. writeIndex = tail.get(); // 0
  120. }
  121. tail.elements[writeIndex] = item;
  122. /**
  123. * 如果使用者在将DefaultHandle对象压入队列后,
  124. * 将Stack设置为null,但是此处的DefaultHandle是持有stack的强引用的,则Stack对象无法回收;
  125. * 而且由于此处DefaultHandle是持有stack的强引用,WeakHashMap中对应stack的WeakOrderQueue也无法被回收掉了,导致内存泄漏。
  126. */
  127. item.stack = null;
  128. // we lazy set to ensure that setting stack to null appears before we unnull it in the owning thread;
  129. // this also means we guarantee visibility of an element in the queue if we see the index updated
  130. // tail本身继承于AtomicInteger,所以此处直接对tail进行+1操作
  131. // why lazySet? https://github.com/netty/netty/issues/8215
  132. tail.lazySet(writeIndex + 1);
  133. // tail.set(writeIndex + 1);
  134. }
  135. /***********************WeakOrderQueue.Head************************/
  136. // Head仅仅作为head-Link的占位符,仅用于ObjectCleaner回收操作
  137. static final class Head {
  138. private final AtomicInteger availableSharedCapacity;
  139. /**
  140. * 指定读操作的Link节点,
  141. * eg. Head -> Link1 -> Link2
  142. * 假设此时的读操作在Link2上进行时,则此处的link == Link2,见transfer(Stack dst),
  143. * 实际上此时Link1已经被读完了,Link1变成了垃圾(一旦一个Link的读指针指向了最后,则该Link不会被重复利用,而是被GC掉,
  144. * 之后回收空间,新建Link再进行操作)
  145. */
  146. private Link link;
  147. private Head(AtomicInteger availableSharedCapacity) {
  148. this.availableSharedCapacity = availableSharedCapacity;
  149. }
  150. /**
  151. * 这里可以理解为一次内存的批量分配,每次从availableSharedCapacity中分配space个大小的内存。
  152. * 如果一个Link中不是放置一个DefaultHandle[],而是只放置一个DefaultHandle,那么此处的space==1,这样的话就需要频繁的进行内存分配
  153. */
  154. private boolean reserveSpace(int space) {
  155. return reserveSpace(availableSharedCapacity, space);
  156. }
  157. private static boolean reserveSpace(AtomicInteger availableSharedCapacity, int space) {
  158. assert space > 0;
  159. // cas + 无限重试进行 分配
  160. for (; ; ) {
  161. int available = availableSharedCapacity.get();
  162. if (available < space) {
  163. return false;
  164. }
  165. // 注意:这里使用的是AtomicInteger,当这里的availableSharedCapacity发生变化时,实际上就是改变的stack.availableSharedCapacity的int value属性的值
  166. if (availableSharedCapacity.compareAndSet(available, available - space)) {
  167. return true;
  168. }
  169. }
  170. }
  171. private void reclaimSpace(int space) {
  172. assert space >= 0;
  173. availableSharedCapacity.addAndGet(space);
  174. }
  175. /**
  176. * 在该对象被真正的回收前,执行该方法
  177. * 循环释放当前的WeakOrderQueue中的Link链表所占用的所有共享空间availableSharedCapacity,
  178. * 如果不释放,否则该值将不会增加,影响后续的Link的创建
  179. */
  180. @Override
  181. protected void finalize() throws Throwable {
  182. try {
  183. super.finalize();
  184. } finally {
  185. Link head = link;
  186. // Unlink to help GC
  187. link = null;
  188. while (head != null) {
  189. reclaimSpace(LINK_CAPACITY);
  190. Link next = head.next;
  191. // Unlink to help GC and guard against GC nepotism.
  192. head.next = null;
  193. head = next;
  194. }
  195. }
  196. }
  197. }
  198. /***********************WeakOrderQueue.Link************************/
  199. static final class Link extends AtomicInteger {
  200. private final DefaultHandle<?>[] elements = new DefaultHandle[LINK_CAPACITY];
  201. /**
  202. * Link的下一个节点
  203. */
  204. private Link next;
  205. public int readIndex;
  206. }

Stack#pushLater 步骤:

  1. 首先获取当前线程的 Map<Stack<?>, WeakOrderQueue> 对象,如果没有就创建一个空 map;

  2. 然后从 map 对象中获取 key 为当前的 Stack 对象的 WeakOrderQueue;

  3. 如果存在,则判断该 queue 是不是 WeakOrderQueue.DUMMY 对象,如果是,直接返回,不回收对象;如果不是则调用 WeakOrderQueue#add(DefaultHandle item) 添加对象;

  4. 如果不存在,则先判断当前的 map 的数量是不是已经达到或者超过 maxDelayedQueues 限制了(默认最大为 2 * cpu 核数个),如果是,则直接塞入一个{当前的 Stack 对象, WeakOrderQueue.DUMMY 对象},最后扔掉当前的 DefaultHandle 对象;如果不是,则会新建 WeakOrderQueue,之后将{当前的 Stack 对象, 创建好的 WeakOrderQueue 对象}添加到map中,最后调用 WeakOrderQueue#add(DefaultHandle item) 添加对象。

WeakOrderQueue 的创建流程:

  1. 首先判断当前的 Stack 对象的可用共享内存(初始默认值为 2k)是否还有足够的空间(LINK_CAPACITY,默认为16)来存放一个 Link,如果不够了,直接返回;否则,使用 cas+ 无限重试的方式设置 Stack 的可用共享内存 = 当前的可用共享内存大小 - 16,也就是分配一个 Link 大小的空间;

  2. 如果分配成功,则会新建一个 WeakOrderQueue 对象:在 WeakOrderQueue 构造器中,首先创建一个 Link 对象,而每一个 Link 对象内部都包含一个 DefaultHandle[LINK_CAPACITY],用于存放回收的对象;然后创建一个 Head 对象,之后将 Head.link 设置为刚刚创建出来的 Link 对象,用于后续的对象 pop 操作;最后设置当前的 WeakOrderQueue 所属的线程为当前线程。

  3. 先将原本的 stack.head 赋值给刚刚创建的 WeakOrderQueue 的 next 节点,之后将刚刚创建的 WeakOrderQueue 设置为 stack.head(这一步非常重要:假设线程 A 创建对象,此处是线程 C 回收对象,则线程 C 先获取其 Map<Stack<?>, WeakOrderQueue> 对象中 key=线程A的stack对象的 WeakOrderQueue,然后将该 Queue 赋值给线程 A 的 stack.head,后续的 pop 操作打基础),形成 WeakOrderQueue 的链表结构。

WeakOrderQueue#add(DefaultHandle item)添加对象流程:

  1. 首先设置 item.lastRecycledId = 当前 WeakOrderQueue 的 id

  2. 然后看当前的 WeakOrderQueue 中的 Link 节点链表中的尾部 Link 节点的 DefaultHandle[] 中的元素个数是否已经达到 LINK_CAPACITY(16)

  3. 如果不是,则直接将当前的 DefaultHandle 元素插入尾部 Link 节点的 DefaultHandle[] 中,之后置空当前的 DefaultHandle 元素的 stack 属性,最后记录当前的 DefaultHandle[] 中的元素数量;

  4. 如果是,则先从当前的可共享容量中划分一块新的 Link 容量,如果不够划分,直接返回;如果够,则新建一个 Link,并且放在当前的 Link 链表中的尾部节点处,与之前的 tail 节点连起来(链表),之后进行第三步的操作。

4.5 从异线程获取对象

  1. /***********************Stack************************/
  2. /**
  3. * cursor:当前操作的WeakOrderQueue
  4. * prev:cursor的前一个WeakOrderQueue
  5. */
  6. private WeakOrderQueue cursor, prev;
  7. DefaultHandle<T> pop() {
  8. int size = this.size;
  9. if (size == 0) {
  10. if (!scavenge()) {
  11. return null;
  12. }
  13. /**
  14. * 由于在transfer(Stack<?> dst)的过程中,可能会将其他线程的WeakOrderQueue中的DefaultHandle对象传递到当前的Stack,
  15. * 所以size发生了变化,需要重新赋值
  16. */
  17. size = this.size;
  18. }
  19. /**
  20. * 注意:因为一个Recycler<T>只能回收一种类型T的对象,所以element可以直接使用操作size来作为下标来进行获取
  21. */
  22. size--;
  23. DefaultHandle<T> ret = elements[size];
  24. // 置空
  25. elements[size] = null;
  26. if (ret.lastRecycledId != ret.recycledId) {
  27. throw new IllegalStateException("recycled multiple times");
  28. }
  29. this.size = size;
  30. // 置位
  31. ret.recycledId = ret.lastRecycledId = 0;
  32. return ret;
  33. }
  34. private boolean scavenge() {
  35. if (scavengeSome()) {
  36. return true;
  37. }
  38. // reset our scavenge cursor
  39. prev = null;
  40. cursor = head;
  41. return false;
  42. }
  43. private boolean scavengeSome() {
  44. WeakOrderQueue prev;
  45. WeakOrderQueue cursor = this.cursor;
  46. if (cursor == null) {
  47. prev = null;
  48. cursor = head;
  49. // 如果head==null,表示当前的Stack对象没有WeakOrderQueue,直接返回
  50. if (cursor == null) {
  51. return false;
  52. }
  53. } else {
  54. prev = this.prev;
  55. }
  56. boolean success = false;
  57. do {
  58. if (cursor.transfer(this)) {
  59. success = true;
  60. break;
  61. }
  62. // 遍历下一个WeakOrderQueue
  63. WeakOrderQueue next = cursor.next;
  64. // 做清理操作
  65. if (cursor.owner.get()==null) {
  66. /**
  67. * 如果当前的WeakOrderQueue的线程已经不可达了,则
  68. * 1、如果该WeakOrderQueue中有数据,则将其中的数据全部转移到当前Stack中
  69. * 2、将当前的WeakOrderQueue的前一个节点prev指向当前的WeakOrderQueue的下一个节点,即将当前的WeakOrderQueue从Queue链表中移除。方便后续GC
  70. */
  71. if (cursor.hasFinalData()){
  72. for (;;){
  73. if (cursor.transfer(this)) {
  74. success =true;
  75. } else {
  76. break;
  77. }
  78. }
  79. }
  80. if (prev != null) {
  81. prev.setNext(next);
  82. }
  83. } else {
  84. prev = cursor;
  85. }
  86. cursor = next;
  87. } while (cursor != null && !success);
  88. this.prev = prev;
  89. this.cursor = cursor;
  90. return success;
  91. }
  92. int increaseCapacity(int expectedCapacity) {
  93. // 获取旧数组长度
  94. int newCapacity = elements.length;
  95. // 获取最大长度
  96. int maxCapacity = this.maxCapacity;
  97. // 不断扩容(每次扩容2倍),直到达到expectedCapacity或者新容量已经大于等于maxCapacity
  98. do {
  99. // 扩容2倍
  100. newCapacity <<= 1;
  101. } while (newCapacity < expectedCapacity && newCapacity < maxCapacity);
  102. // 上述的扩容有可能使新容量newCapacity>maxCapacity,这里取最小值
  103. newCapacity = Math.min(newCapacity, maxCapacity);
  104. // 如果新旧容量不相等,进行实际扩容
  105. if (newCapacity != elements.length) {
  106. // 创建新数组,复制旧数组元素到新数组,并将新数组赋值给Stack.elements
  107. elements = Arrays.copyOf(elements, newCapacity);
  108. }
  109. return newCapacity;
  110. }
  111. /***********************WeakOrderQueue************************/
  112. public <T> boolean transfer(Stack<T> dst) {
  113. // 寻找第一个Link(Head不是Link)
  114. Link head = this.head.link;
  115. // head == null,表示只有Head一个节点,没有存储数据的节点,直接返回
  116. if (head == null) {
  117. return false;
  118. }
  119. // 如果第一个Link节点的readIndex索引已经到达该Link对象的DefaultHandle[]的尾部,
  120. // 则判断当前的Link节点的下一个节点是否为null,如果为null,说明已经达到了Link链表尾部,直接返回,
  121. // 否则,将当前的Link节点的下一个Link节点赋值给head和this.head.link,进而对下一个Link节点进行操作
  122. if (head.readIndex == LINK_CAPACITY) {
  123. if (head.next == null) {
  124. return false;
  125. }
  126. this.head.link = head = head.next;
  127. }
  128. // 获取Link节点的readIndex,即当前的Link节点的第一个有效元素的位置
  129. int srcStart = head.readIndex;
  130. // 获取Link节点的writeIndex,即当前的Link节点的最后一个有效元素的位置
  131. int srcEnd = head.get();
  132. // 计算Link节点中可以被转移的元素个数
  133. int srcSize = srcEnd - srcStart;
  134. if (srcSize == 0) {
  135. return false;
  136. }
  137. // 获取转移元素的目的地Stack中当前的元素个数
  138. final int dstSize = dst.size;
  139. // 计算期盼的容量
  140. final int expectedCapacity = dstSize + srcSize;
  141. /**
  142. * 如果expectedCapacity大于目的地Stack的长度
  143. * 1、对目的地Stack进行扩容
  144. * 2、计算Link中最终的可转移的最后一个元素的下标
  145. */
  146. if (expectedCapacity > dst.elements.length) {
  147. int actualCapacity = dst.increaseCapacity(expectedCapacity);
  148. srcEnd = Math.min(srcEnd, actualCapacity - dstSize + srcStart);
  149. }
  150. if (srcStart == srcEnd) {
  151. // The destination stack is full already.
  152. return false;
  153. } else {
  154. // 获取Link节点的DefaultHandle[]
  155. final DefaultHandle[] srcElems = head.elements;
  156. // 获取目的地Stack的DefaultHandle[]
  157. final DefaultHandle[] dstElems = dst.elements;
  158. // dst数组的大小,会随着元素的迁入而增加,如果最后发现没有增加,那么表示没有迁移成功任何一个元素
  159. int newDstSize = dstSize;
  160. for (int i = srcStart; i < srcEnd; i++) {
  161. final DefaultHandle element = srcElems[i];
  162. /**
  163. * 设置element.recycleId 或者 进行防护性判断
  164. */
  165. if (element.recycledId == 0) {
  166. element.recycledId = element.lastRecycledId;
  167. } else if (element.recycledId != element.lastRecycledId) {
  168. throw new IllegalStateException("recycled already");
  169. }
  170. // 置空Link节点的DefaultHandle[i]
  171. srcElems[i] = null;
  172. // 扔掉放弃7/8的元素
  173. if (dst.dropHandle(element)) {
  174. continue;
  175. }
  176. // 将可转移成功的DefaultHandle元素的stack属性设置为目的地Stack
  177. element.stack = dst;
  178. // 将DefaultHandle元素转移到目的地Stack的DefaultHandle[newDstSize ++]中
  179. dstElems[newDstSize++] = element;
  180. }
  181. if (srcEnd == LINK_CAPACITY && head.next != null) {
  182. this.head.reclaimSpace(LINK_CAPACITY);
  183. // 将Head指向下一个Link,也就是将当前的Link给回收掉了
  184. // 假设之前为Head -> Link1 -> Link2,回收之后为Head -> Link2
  185. this.head.link = head.next;
  186. }
  187. // 重置readIndex
  188. head.readIndex = srcEnd;
  189. // 表示没有被回收任何一个对象,直接返回
  190. if (dst.size == newDstSize) {
  191. return false;
  192. }
  193. // 将新的newDstSize赋值给目的地Stack的size
  194. dst.size = newDstSize;
  195. return true;
  196. }
  197. }
  198. private boolean hasFinalData() {
  199. return tail.readIndex != tail.get();
  200. }

总体步骤:

  1. 首先获取当前的 Stack 中的 DefaultHandle 对象中的元素个数。

  2. 如果为 0,则从其他线程的与当前的 Stack 对象关联的 WeakOrderQueue 中获取元素,并转移到 Stack 的 DefaultHandle[] 中(每一次 pop 只转移一个有元素的 Link),如果转移不成功,说明没有元素可用,直接返回 null;如果转移成功,则重置 size属性 = 转移后的 Stack 的 DefaultHandle[] 的 size,之后直接获取 Stack 对象中 DefaultHandle[] 的最后一位元素,然后将该元素置为 null,之后做防护性检测,最后重置当前的 stack 对象的 size 属性以及获取到的 DefaultHandle 对象的 recycledId 和 lastRecycledId 回收标记,返回 DefaultHandle 对象。

scavenge()转移的流程:

  1. 首先设置当前操作的 WeakOrderQueue cursor,如果为 null,则赋值为 stack.head 节点,如果 stack.head 为 null,则表明外部线程没有回收过当前线程创建的 User 对象,则直接返回 false;如果不为 null,则继续向下执行;

  2. 首先对当前的 cursor 进行元素的转移,如果转移成功,则跳出循环,设置 prev 和 cursor 属性;如果转移不成功,获取下一个线程 Y 中的与当前线程的 Stack 对象关联的 WeakOrderQueue,如果该 queue 所属的线程 Y 还可达,则直接设置 cursor 为该 queue,进行下一轮循环;如果该 queue 所属的线程 Y 不可达了,则判断其内是否还有元素,如果有,全部转移到当前线程的 Stack 中,之后将线程 Y 的 queue 从查询 queue 链表中移除。

元素转移的细节流程:

  1. 寻找 cursor 节点中的第一个 Link(即 Head.link,注意:Head 不是 Link),如果为 null,则表示没有数据,直接返回;否则,

  2. 如果第一个 Link 节点的 readIndex 索引已经到达该 Link 对象的 DefaultHandle[] 的尾部,则判断当前的 Link 节点的下一个节点是否为 null,如果为 null,说明已经达到了 Link 链表尾部,直接返回,否则,将当前的 Link 节点的下一个 Link 节点赋值给 head 和 this.head.link,进而对下一个 Link 节点进行操作;

  3. 获取 Link 节点的 readIndex,即当前的 Link 节点的第一个有效元素的位置

  4. 获取 Link 节点的 writeIndex,即当前的 Link 节点的最后一个有效元素的位置

  5. 计算 Link 节点中可以被转移的元素个数,如果为 0,表示没有可转移的元素,直接返回,否则;

  6. 获取转移元素的目的地 Stack 中当前的元素个数并计算期盼的容量 expectedCapacity,如果 expectedCapacity 大于目的地 Stack 的长度,则先对目的地 Stack 进行扩容,计算 Link 中最终的可转移的最后一个元素的下标;

  7. 如果发现目的地 Stack 已经满了,则直接返回 false;否则

  8. 获取 Link 节点的 DefaultHandle[] 和目的地 Stack 的 DefaultHandle[]

  9. 根据可转移的起始位置和结束位置对 Link 节点的 DefaultHandle[] 进行循环操作:

  10. 置空 Link 节点的 DefaultHandle[i],扔掉放弃 7/8 的元素,将可转移成功的 DefaultHandle 元素的stack属性设置为目的地 Stack,最后将 DefaultHandle 元素转移到目的地 Stack 的 DefaultHandle[newDstSize++] 中

  11. 如果当前被遍历的 Link 节点的 DefaultHandle[] 已经被掏空了,并且该 Link 节点还有下一个 Link 节点,则先将当前的 Link 节点所占的内存空间释放(即为可共享空间 + LINK_CAPACITY),之后将当前的 Link 节点从 Link 链表中删除;

  12. 重置当前 Link 的 readIndex

  13. 如果上述一票操作后,没有被回收任何一个对象,直接返回 fasle,否则将新的 newDstSize 设置给 stack.size,返回 true。

所以,每一次转移其实只会针对一个有元素的 Link 进行操作,这样就不会太影响查找性能。

五、流程总结

5.1 同线程获取对象

5.2 同线程回收对象

5.3 异线程回收对象

5.4 从异线程获取对象

六、线程同步问题

  1. 假设线程 A 进行 get,线程 B 也进行 get,无锁(二者各自从自己的 stack 或者从各自的 weakOrderQueue 中进行获取)

  2. 假设线程 A 进行 get 对象 X,线程 B 进行 recycle 对象 X,无锁(假设线程 A 无法直接从其 Stack 获取,从 WeakOrderQueue 进行获取,由于 stack.head 是 volatile 的,线程 B recycle 的对象 X 可以被线程 A 立即获取)

  3. 假设线程 C 和线程 B recycle 线程 A 的对象 X,此时需要加锁:假设线程 B 和线程 C 同时回收线程 A 的对象时,有可能会同时 newQueue,就可能同时 setHead,所以这里需要加锁。以 head == null 的时候为例,

    • 加锁:线程 B 先执行,则 head = 线程B的queue;之后线程 C 执行,此时将当前的 head 也就是线程 B 的 queue 作为线程 C 的 queue 的 next,组成链表,之后设置 head 为线程 C 的 queue

    • 不加锁:线程 B 先执行 queue.setNext(head),此时线程 B 的 queue.next = null;然后线程 C 执行 queue.setNext(head),线程 C 的 queue.next = null;线程 B 执行 head = queue,设置 head 为线程 B 的 queue;线程 C 执行 head = queue,设置 head 为线程 C 的 queue,注意:此时线程 B 和线程 C 的 queue 没有连起来,则之后的 pop() 就不会从 B 进行查询。(B 就是资源泄露)

所以,可以看出在实际使用中,Recycler 几乎是一个无锁的对象回收池。

七、防止资源泄露的措施

  1. 当我们删除 FastThreadLocal<Stack> threadLocal 中线程 A 的 stack 对象时,会从 FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED 对象(注意:该对象是类变量)中删除 key 为 stack 对象的 Entry;

  2. FastThreadLocal<Map<Stack, WeakOrderQueue>> DELAYED_RECYCLED 使用 WeakHashMap:当 Stack 没有强引用可达时,整个 Entry{Stack, WeakOrderQueue} 都会加入相应的弱引用队列等待回收;

  3. Stack.WeakReference threadRef:假设该线程对象在外界已经没有强引用了,那么实际上该线程对象就可以被回收了。但是如果 Stack 用的是强引用,那么虽然外界不再对该线程有强引用,但是该 stack 对象还持有强引用(假设用户存储了 DefaultHandle 对象,然后一直不释放,而 DefaultHandle 对象又持有 stack 引用),导致该线程对象无法释放。

  4. WeakOrderQueue.WeakReference owner:与 Stack.WeakReference threadRef 相同。

    将 DefaultHandle item 添加到 WeakOrderQueue中时,需要 item.stack = null:如果使用者在将 DefaultHandle 对象压入队列后,将 Stack 设置为 null,但是此处的 DefaultHandle 是持有 stack 的强引用的,则 Stack 对象无法回收;而且由于此处 DefaultHandle 是持有 stack 的强引用,WeakHashMap 中对应 stack 的 WeakOrderQueue 也无法被回收掉了,导致内存泄漏。

  5. Head.finalize():在该对象被真正的回收前,会循环释放当前的 WeakOrderQueue 中的 Link 链表所占用的所有共享空间 availableSharedCapacity,如果不释放,否则该值将不会增加,影响后续的Link的创建

附、bug 记录https://github.com/netty/netty/issues/8220,该bug将会在4.1.30的版本中修复。

本文转载自 netty源码分析4 - Recycler对象池的设计

参考:

  1. 《Netty 对象池实践优化》:https://blog.csdn.net/zhousenshan/article/details/82942381
  2. 《Netty轻量级对象池实现分析》:https://www.cnblogs.com/hzmark/p/netty-object-pool.html

每天用心记录一点点。内容也许不重要,但习惯很重要!

netty源码分析 - Recycler 对象池的设计的更多相关文章

  1. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第4节: recycler中获取对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第四节: recycler中获取对象 这一小节剖析如何从对象回收站中获取对象: 我们回顾上一小节demo的ma ...

  2. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第5节: 同线程回收对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第五节: 同线程回收对象 上一小节剖析了从recycler中获取一个对象, 这一小节分析在创建和回收是同线程的 ...

  3. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第6节: 异线程回收对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第六节: 异线程回收对象 异线程回收对象, 就是创建对象和回收对象不在同一条线程的情况下, 对象回收的逻辑 我 ...

  4. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第7节: 获取异线程释放的对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第七节: 获取异线程释放的对象 上一小节分析了异线程回收对象, 原理是通过与stack关联的WeakOrder ...

  5. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第3节: recycler的使用和创建

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第三节: recycler的使用和创建   这一小节开始学习recycler相关的知识, recycler是n ...

  6. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第1节: FastThreadLocal的使用和创建

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 概述: FastThreadLocal我们在剖析堆外内存分配的时候简单介绍过, 它类似于JDK的ThreadL ...

  7. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第2节: FastThreadLocal的set方法

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第二节: FastThreadLocal的set方法 上一小节我们学习了FastThreadLocal的创建和 ...

  8. Netty源码分析(前言, 概述及目录)

    Netty源码分析(完整版) 前言 前段时间公司准备改造redis的客户端, 原生的客户端是阻塞式链接, 并且链接池初始化的链接数并不高, 高并发场景会有获取不到连接的尴尬, 所以考虑了用netty长 ...

  9. Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述

    Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上一小节简单分析了PooledByteBufAllocator中, 线程局部缓存和arean的相关逻辑, 这 ...

随机推荐

  1. 2019牛客多校第五场B-generator 1(矩阵快速幂)

    generator 1 题目传送门 解题思路 矩阵快速幂.只是平时的矩阵快速幂是二进制的,这题要用十进制的快速幂. 代码如下 #include <bits/stdc++.h> #defin ...

  2. python基础【第七篇】

    字典 列表可以存储大量的数据类型,但是只能按照顺序存储,数据与数据之间关联性不强. 所以咱们需要引入一种容器型的数据类型,解决上面的问题,这就需要dict字典. 字典(dict)是python中唯⼀的 ...

  3. [POI2010]OWC-Sheep

    题目 不难猜到或者发现的性质,如果连了一条对角线划分出了奇数个点,那么这条对角线肯定不合法:因为划分成三角形就不可能有对角线相交,于是划分成奇数的那一边怎么样也不可能划分成全是偶数 于是我们需要对每一 ...

  4. TurtleBOT3

    ubuntu更换源 sudo cp /etc/apt/sources.list /etc/apt/sources_backup.list sudo gedit /etc/apt/sources.lis ...

  5. 微信小程序の模板

    一.什么是模板 模板顾名思义就是可以复用的代码块.减少编码工作量. 二.例子 <template name="templateTest"> <view>th ...

  6. 微信小程序 滚动到底部

    1.html <view id="bottom"></view> 2. onReady: function () { //滚动到底部 let query = ...

  7. sql 合并查询结果

    在使用js报表工具的时候,常常需要提供json数据进行显示, 在sql查询的时候就需要构造合适的查询结果出来; 就用到了合并两个没有关联关系的表数据, SELECT SUM(a1.amount) AS ...

  8. java中文乱码转换

    String str=URLDecoder.decode(String, "UTF-8")

  9. Dubbo---zookeeper 注册中心---xml配置

    1.项目结构(maven项目) 2.pom <?xml version="1.0" encoding="UTF-8"?> <project x ...

  10. 接口使用Http发送post请求工具类

    HttpClientKit import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamR ...