View Javadoc
1   /*
2    * Copyright 2025 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.util.internal;
17  
18  import io.netty.util.IllegalReferenceCountException;
19  
20  import java.lang.invoke.MethodHandles;
21  import java.lang.invoke.VarHandle;
22  import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
23  
24  import static io.netty.util.internal.ObjectUtil.checkPositive;
25  
26  /**
27   * Monomorphic reference counter implementation that always use the most efficient available atomic updater.
28   * This implementation is easier for the JIT compiler to optimize,
29   * compared to when {@link ReferenceCountUpdater} is used.
30   */
31  @SuppressWarnings("deprecation")
32  public final class RefCnt {
33  
34      private static final int UNSAFE = 0;
35      private static final int VAR_HANDLE = 1;
36      private static final int ATOMIC_UPDATER = 2;
37      private static final int REF_CNT_IMPL;
38  
39      static {
40          if (PlatformDependent.hasUnsafe()) {
41              REF_CNT_IMPL = UNSAFE;
42          } else if (PlatformDependent.hasVarHandle()) {
43              REF_CNT_IMPL = VAR_HANDLE;
44          } else {
45              REF_CNT_IMPL = ATOMIC_UPDATER;
46          }
47      }
48  
49      /*
50       * Implementation notes:
51       *
52       * For the updated int field:
53       *   Even => "real" refcount is (refCnt >>> 1)
54       *   Odd  => "real" refcount is 0
55       *
56       * This field is package-private so that the AtomicRefCnt implementation can reach it, even on native-image.
57       */
58      volatile int value;
59  
60      public RefCnt() {
61          switch (REF_CNT_IMPL) {
62          case UNSAFE:
63              UnsafeRefCnt.init(this);
64              break;
65          case VAR_HANDLE:
66              VarHandleRefCnt.init(this);
67              break;
68          case ATOMIC_UPDATER:
69          default:
70              AtomicRefCnt.init(this);
71              break;
72          }
73      }
74  
75      /**
76       * Returns the current reference count of the given {@code RefCnt} instance with a load acquire semantic.
77       *
78       * @param ref the target RefCnt instance
79       * @return the reference count
80       */
81      public static int refCnt(RefCnt ref) {
82          switch (REF_CNT_IMPL) {
83          case UNSAFE:
84              return UnsafeRefCnt.refCnt(ref);
85          case VAR_HANDLE:
86              return VarHandleRefCnt.refCnt(ref);
87          case ATOMIC_UPDATER:
88          default:
89              return AtomicRefCnt.refCnt(ref);
90          }
91      }
92  
93      /**
94       * Increases the reference count of the given {@code RefCnt} instance by 1.
95       *
96       * @param ref the target RefCnt instance
97       */
98      public static void retain(RefCnt ref) {
99          switch (REF_CNT_IMPL) {
100         case UNSAFE:
101             UnsafeRefCnt.retain(ref);
102             break;
103         case VAR_HANDLE:
104             VarHandleRefCnt.retain(ref);
105             break;
106         case ATOMIC_UPDATER:
107         default:
108             AtomicRefCnt.retain(ref);
109             break;
110         }
111     }
112 
113     /**
114      * Increases the reference count of the given {@code RefCnt} instance by the specified increment.
115      *
116      * @param ref       the target RefCnt instance
117      * @param increment the amount to increase the reference count by
118      * @throws IllegalArgumentException if increment is not positive
119      */
120     public static void retain(RefCnt ref, int increment) {
121         switch (REF_CNT_IMPL) {
122         case UNSAFE:
123             UnsafeRefCnt.retain(ref, increment);
124             break;
125         case VAR_HANDLE:
126             VarHandleRefCnt.retain(ref, increment);
127             break;
128         case ATOMIC_UPDATER:
129         default:
130             AtomicRefCnt.retain(ref, increment);
131             break;
132         }
133     }
134 
135     /**
136      * Decreases the reference count of the given {@code RefCnt} instance by 1.
137      *
138      * @param ref the target RefCnt instance
139      * @return true if the reference count became 0 and the object should be deallocated
140      */
141     public static boolean release(RefCnt ref) {
142         switch (REF_CNT_IMPL) {
143         case UNSAFE:
144             return UnsafeRefCnt.release(ref);
145         case VAR_HANDLE:
146             return VarHandleRefCnt.release(ref);
147         case ATOMIC_UPDATER:
148         default:
149             return AtomicRefCnt.release(ref);
150         }
151     }
152 
153     /**
154      * Decreases the reference count of the given {@code RefCnt} instance by the specified decrement.
155      *
156      * @param ref       the target RefCnt instance
157      * @param decrement the amount to decrease the reference count by
158      * @return true if the reference count became 0 and the object should be deallocated
159      * @throws IllegalArgumentException if decrement is not positive
160      */
161     public static boolean release(RefCnt ref, int decrement) {
162         switch (REF_CNT_IMPL) {
163         case UNSAFE:
164             return UnsafeRefCnt.release(ref, decrement);
165         case VAR_HANDLE:
166             return VarHandleRefCnt.release(ref, decrement);
167         case ATOMIC_UPDATER:
168         default:
169             return AtomicRefCnt.release(ref, decrement);
170         }
171     }
172 
173     /**
174      * Returns {@code true} if and only if the given reference counter is alive.
175      * This method is useful to check if the object is alive without incurring the cost of a volatile read.
176      *
177      * @param ref the target RefCnt instance
178      * @return {@code true} if alive
179      */
180     public static boolean isLiveNonVolatile(RefCnt ref) {
181         switch (REF_CNT_IMPL) {
182         case UNSAFE:
183             return UnsafeRefCnt.isLiveNonVolatile(ref);
184         case VAR_HANDLE:
185             return VarHandleRefCnt.isLiveNonVolatile(ref);
186         case ATOMIC_UPDATER:
187         default:
188             return AtomicRefCnt.isLiveNonVolatile(ref);
189         }
190     }
191 
192     /**
193      * <strong>WARNING:</strong>
194      * An unsafe operation that sets the reference count of the given {@code RefCnt} instance directly.
195      *
196      * @param ref    the target RefCnt instance
197      * @param refCnt new reference count
198      */
199     public static void setRefCnt(RefCnt ref, int refCnt) {
200         switch (REF_CNT_IMPL) {
201         case UNSAFE:
202             UnsafeRefCnt.setRefCnt(ref, refCnt);
203             break;
204         case VAR_HANDLE:
205             VarHandleRefCnt.setRefCnt(ref, refCnt);
206             break;
207         case ATOMIC_UPDATER:
208         default:
209             AtomicRefCnt.setRefCnt(ref, refCnt);
210             break;
211         }
212     }
213 
214     /**
215      * Resets the reference count of the given {@code RefCnt} instance to 1.
216      * <p>
217      * <strong>Warning:</strong> This method uses release memory semantics, meaning the change may not be
218      * immediately visible to other threads. It should only be used in quiescent states where no other
219      * threads are accessing the reference count.
220      *
221      * @param ref the target RefCnt instance
222      */
223     public static void resetRefCnt(RefCnt ref) {
224         switch (REF_CNT_IMPL) {
225         case UNSAFE:
226             UnsafeRefCnt.resetRefCnt(ref);
227             break;
228         case VAR_HANDLE:
229             VarHandleRefCnt.resetRefCnt(ref);
230             break;
231         case ATOMIC_UPDATER:
232         default:
233             AtomicRefCnt.resetRefCnt(ref);
234             break;
235         }
236     }
237 
238     static void throwIllegalRefCountOnRelease(int decrement, int curr) {
239         throw new IllegalReferenceCountException(curr >>> 1, -(decrement >>> 1));
240     }
241 
242     private static final class AtomicRefCnt {
243         private static final AtomicIntegerFieldUpdater<RefCnt> UPDATER =
244                 AtomicIntegerFieldUpdater.newUpdater(RefCnt.class, "value");
245 
246         static void init(RefCnt instance) {
247             UPDATER.set(instance, 2);
248         }
249 
250         static int refCnt(RefCnt instance) {
251             return UPDATER.get(instance) >>> 1;
252         }
253 
254         static void retain(RefCnt instance) {
255             retain0(instance, 2);
256         }
257 
258         static void retain(RefCnt instance, int increment) {
259             retain0(instance, checkPositive(increment, "increment") << 1);
260         }
261 
262         private static void retain0(RefCnt instance, int increment) {
263             // oldRef & 0x80000001 stands for oldRef < 0 || oldRef is odd
264             // NOTE: we're optimizing for inlined and constant folded increment here -> which will make
265             // Integer.MAX_VALUE - increment to be computed at compile time
266             int oldRef = UPDATER.getAndAdd(instance, increment);
267             if ((oldRef & 0x80000001) != 0 || oldRef > Integer.MAX_VALUE - increment) {
268                 UPDATER.getAndAdd(instance, -increment);
269                 throw new IllegalReferenceCountException(0, increment >>> 1);
270             }
271         }
272 
273         static boolean release(RefCnt instance) {
274             return release0(instance, 2);
275         }
276 
277         static boolean release(RefCnt instance, int decrement) {
278             return release0(instance, checkPositive(decrement, "decrement") << 1);
279         }
280 
281         private static boolean release0(RefCnt instance, int decrement) {
282             int curr, next;
283             do {
284                 curr = instance.value;
285                 if (curr == decrement) {
286                     next = 1;
287                 } else {
288                     if (curr < decrement || (curr & 1) == 1) {
289                         throwIllegalRefCountOnRelease(decrement, curr);
290                     }
291                     next = curr - decrement;
292                 }
293             } while (!UPDATER.compareAndSet(instance, curr, next));
294             return (next & 1) == 1;
295         }
296 
297         static void setRefCnt(RefCnt instance, int refCnt) {
298             int rawRefCnt = refCnt > 0? refCnt << 1 : 1;
299             UPDATER.lazySet(instance, rawRefCnt);
300         }
301 
302         static void resetRefCnt(RefCnt instance) {
303             UPDATER.lazySet(instance, 2);
304         }
305 
306         static boolean isLiveNonVolatile(RefCnt instance) {
307             final int rawCnt = instance.value;
308             if (rawCnt == 2) {
309                 return true;
310             }
311             return (rawCnt & 1) == 0;
312         }
313     }
314 
315     private static final class VarHandleRefCnt {
316 
317         private static final VarHandle VH;
318 
319         static {
320             VH = PlatformDependent.findVarHandleOfIntField(MethodHandles.lookup(), RefCnt.class, "value");
321         }
322 
323         static void init(RefCnt instance) {
324             VH.set(instance, 2);
325             VarHandle.storeStoreFence();
326         }
327 
328         static int refCnt(RefCnt instance) {
329             return (int) VH.getAcquire(instance) >>> 1;
330         }
331 
332         static void retain(RefCnt instance) {
333             retain0(instance, 2);
334         }
335 
336         static void retain(RefCnt instance, int increment) {
337             retain0(instance, checkPositive(increment, "increment") << 1);
338         }
339 
340         private static void retain0(RefCnt instance, int increment) {
341             // oldRef & 0x80000001 stands for oldRef < 0 || oldRef is odd
342             // NOTE: we're optimizing for inlined and constant folded increment here -> which will make
343             // Integer.MAX_VALUE - increment to be computed at compile time
344             int oldRef = (int) VH.getAndAdd(instance, increment);
345             if ((oldRef & 0x80000001) != 0 || oldRef > Integer.MAX_VALUE - increment) {
346                 VH.getAndAdd(instance, -increment);
347                 throw new IllegalReferenceCountException(0, increment >>> 1);
348             }
349         }
350 
351         static boolean release(RefCnt instance) {
352             return release0(instance, 2);
353         }
354 
355         static boolean release(RefCnt instance, int decrement) {
356             return release0(instance, checkPositive(decrement, "decrement") << 1);
357         }
358 
359         private static boolean release0(RefCnt instance, int decrement) {
360             int curr, next;
361             do {
362                 curr = (int) VH.get(instance);
363                 if (curr == decrement) {
364                     next = 1;
365                 } else {
366                     if (curr < decrement || (curr & 1) == 1) {
367                         throwIllegalRefCountOnRelease(decrement, curr);
368                     }
369                     next = curr - decrement;
370                 }
371             } while (!(boolean) VH.compareAndSet(instance, curr, next));
372             return (next & 1) == 1;
373         }
374 
375         static void setRefCnt(RefCnt instance, int refCnt) {
376             int rawRefCnt = refCnt > 0? refCnt << 1 : 1;
377             VH.setRelease(instance, rawRefCnt);
378         }
379 
380         static void resetRefCnt(RefCnt instance) {
381             VH.setRelease(instance, 2);
382         }
383 
384         static boolean isLiveNonVolatile(RefCnt instance) {
385             final int rawCnt = (int) VH.get(instance);
386             if (rawCnt == 2) {
387                 return true;
388             }
389             return (rawCnt & 1) == 0;
390         }
391     }
392 
393     private static final class UnsafeRefCnt {
394 
395         private static final long VALUE_OFFSET = getUnsafeOffset(RefCnt.class, "value");
396 
397         private static long getUnsafeOffset(Class<?> clz, String fieldName) {
398             try {
399                 if (PlatformDependent.hasUnsafe()) {
400                     return PlatformDependent.objectFieldOffset(clz.getDeclaredField(fieldName));
401                 }
402             } catch (Throwable ignore) {
403                 // fall-back
404             }
405             return -1;
406         }
407 
408         static void init(RefCnt instance) {
409             PlatformDependent.safeConstructPutInt(instance, VALUE_OFFSET, 2);
410         }
411 
412         static int refCnt(RefCnt instance) {
413             return PlatformDependent.getVolatileInt(instance, VALUE_OFFSET) >>> 1;
414         }
415 
416         static void retain(RefCnt instance) {
417             retain0(instance, 2);
418         }
419 
420         static void retain(RefCnt instance, int increment) {
421             retain0(instance, checkPositive(increment, "increment") << 1);
422         }
423 
424         private static void retain0(RefCnt instance, int increment) {
425             // oldRef & 0x80000001 stands for oldRef < 0 || oldRef is odd
426             // NOTE: we're optimizing for inlined and constant folded increment here -> which will make
427             // Integer.MAX_VALUE - increment to be computed at compile time
428             int oldRef = PlatformDependent.getAndAddInt(instance, VALUE_OFFSET, increment);
429             if ((oldRef & 0x80000001) != 0 || oldRef > Integer.MAX_VALUE - increment) {
430                 PlatformDependent.getAndAddInt(instance, VALUE_OFFSET, -increment);
431                 throw new IllegalReferenceCountException(0, increment >>> 1);
432             }
433         }
434 
435         static boolean release(RefCnt instance) {
436             return release0(instance, 2);
437         }
438 
439         static boolean release(RefCnt instance, int decrement) {
440             return release0(instance, checkPositive(decrement, "decrement") << 1);
441         }
442 
443         private static boolean release0(RefCnt instance, int decrement) {
444             int curr, next;
445             do {
446                 curr = PlatformDependent.getInt(instance, VALUE_OFFSET);
447                 if (curr == decrement) {
448                     next = 1;
449                 } else {
450                     if (curr < decrement || (curr & 1) == 1) {
451                         throwIllegalRefCountOnRelease(decrement, curr);
452                     }
453                     next = curr - decrement;
454                 }
455             } while (!PlatformDependent.compareAndSwapInt(instance, VALUE_OFFSET, curr, next));
456             return (next & 1) == 1;
457         }
458 
459         static void setRefCnt(RefCnt instance, int refCnt) {
460             int rawRefCnt = refCnt > 0? refCnt << 1 : 1;
461             PlatformDependent.putOrderedInt(instance, VALUE_OFFSET, rawRefCnt);
462         }
463 
464         static void resetRefCnt(RefCnt instance) {
465             PlatformDependent.putOrderedInt(instance, VALUE_OFFSET, 2);
466         }
467 
468         static boolean isLiveNonVolatile(RefCnt instance) {
469             final int rawCnt = PlatformDependent.getInt(instance, VALUE_OFFSET);
470             if (rawCnt == 2) {
471                 return true;
472             }
473             return (rawCnt & 1) == 0;
474         }
475     }
476 }