View Javadoc
1   /*
2    * Copyright 2013 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.concurrent.FastThreadLocalThread;
19  import io.netty.util.internal.logging.InternalLogger;
20  import io.netty.util.internal.logging.InternalLoggerFactory;
21  import sun.misc.Unsafe;
22  
23  import java.lang.reflect.Constructor;
24  import java.lang.reflect.Field;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.nio.Buffer;
28  import java.nio.ByteBuffer;
29  import java.security.AccessController;
30  import java.security.PrivilegedAction;
31  import java.util.concurrent.atomic.AtomicLong;
32  
33  import static io.netty.util.internal.ObjectUtil.checkNotNull;
34  /**
35   * The {@link PlatformDependent} operations which requires access to {@code sun.misc.*}.
36   */
37  @SuppressJava6Requirement(reason = "Unsafe access is guarded")
38  final class PlatformDependent0 {
39  
40      private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent0.class);
41      private static final long ADDRESS_FIELD_OFFSET;
42      private static final long BYTE_ARRAY_BASE_OFFSET;
43      private static final long INT_ARRAY_BASE_OFFSET;
44      private static final long INT_ARRAY_INDEX_SCALE;
45      private static final long LONG_ARRAY_BASE_OFFSET;
46      private static final long LONG_ARRAY_INDEX_SCALE;
47      private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
48      private static final Method ALLOCATE_ARRAY_METHOD;
49      private static final Method ALIGN_SLICE;
50      private static final int JAVA_VERSION = javaVersion0();
51      private static final boolean IS_ANDROID = isAndroid0();
52      private static final Throwable EXPLICIT_NO_UNSAFE_CAUSE = explicitNoUnsafeCause0();
53      private static final boolean STORE_FENCE_AVAILABLE;
54  
55      private static final Throwable UNSAFE_UNAVAILABILITY_CAUSE;
56      private static final Object INTERNAL_UNSAFE;
57  
58      // See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/
59      // ImageInfo.java
60      private static final boolean RUNNING_IN_NATIVE_IMAGE = SystemPropertyUtil.contains(
61              "org.graalvm.nativeimage.imagecode");
62  
63      private static final boolean IS_EXPLICIT_TRY_REFLECTION_SET_ACCESSIBLE = explicitTryReflectionSetAccessible0();
64  
65      // Package-private for testing.
66      static final Method IS_VIRTUAL_THREAD_METHOD = getIsVirtualThreadMethod();
67      // Package-private for testing.
68      static final Class<?> BASE_VIRTUAL_THREAD_CLASS = getBaseVirtualThreadClass();
69  
70      static final Unsafe UNSAFE;
71  
72      // constants borrowed from murmur3
73      static final int HASH_CODE_ASCII_SEED = 0xc2b2ae35;
74      static final int HASH_CODE_C1 = 0xcc9e2d51;
75      static final int HASH_CODE_C2 = 0x1b873593;
76  
77      /**
78       * Limits the number of bytes to copy per {@link Unsafe#copyMemory(long, long, long)} to allow safepoint polling
79       * during a large copy.
80       */
81      private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;
82  
83      private static final boolean UNALIGNED;
84  
85      private static final long BITS_MAX_DIRECT_MEMORY;
86  
87      static {
88          final ByteBuffer direct;
89          Field addressField = null;
90          Method allocateArrayMethod = null;
91          Throwable unsafeUnavailabilityCause = null;
92          Unsafe unsafe;
93          Object internalUnsafe = null;
94          boolean storeFenceAvailable = false;
95          if ((unsafeUnavailabilityCause = EXPLICIT_NO_UNSAFE_CAUSE) != null) {
96              direct = null;
97              addressField = null;
98              unsafe = null;
99              internalUnsafe = null;
100         } else {
101             direct = ByteBuffer.allocateDirect(1);
102 
103             // attempt to access field Unsafe#theUnsafe
104             final Object maybeUnsafe = AccessController.doPrivileged(new PrivilegedAction<Object>() {
105                 @Override
106                 public Object run() {
107                     try {
108                         final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
109                         // We always want to try using Unsafe as the access still works on java9 as well and
110                         // we need it for out native-transports and many optimizations.
111                         Throwable cause = ReflectionUtil.trySetAccessible(unsafeField, false);
112                         if (cause != null) {
113                             return cause;
114                         }
115                         // the unsafe instance
116                         return unsafeField.get(null);
117                     } catch (NoSuchFieldException e) {
118                         return e;
119                     } catch (SecurityException e) {
120                         return e;
121                     } catch (IllegalAccessException e) {
122                         return e;
123                     } catch (NoClassDefFoundError e) {
124                         // Also catch NoClassDefFoundError in case someone uses for example OSGI and it made
125                         // Unsafe unloadable.
126                         return e;
127                     }
128                 }
129             });
130 
131             // the conditional check here can not be replaced with checking that maybeUnsafe
132             // is an instanceof Unsafe and reversing the if and else blocks; this is because an
133             // instanceof check against Unsafe will trigger a class load and we might not have
134             // the runtime permission accessClassInPackage.sun.misc
135             if (maybeUnsafe instanceof Throwable) {
136                 unsafe = null;
137                 unsafeUnavailabilityCause = (Throwable) maybeUnsafe;
138                 if (logger.isTraceEnabled()) {
139                     logger.debug("sun.misc.Unsafe.theUnsafe: unavailable", (Throwable) maybeUnsafe);
140                 } else {
141                     logger.debug("sun.misc.Unsafe.theUnsafe: unavailable: {}", ((Throwable) maybeUnsafe).getMessage());
142                 }
143             } else {
144                 unsafe = (Unsafe) maybeUnsafe;
145                 logger.debug("sun.misc.Unsafe.theUnsafe: available");
146             }
147 
148             // ensure the unsafe supports all necessary methods to work around the mistake in the latest OpenJDK,
149             // or that they haven't been removed by JEP 471.
150             // https://github.com/netty/netty/issues/1061
151             // https://www.mail-archive.com/[email protected]/msg00698.html
152             // https://openjdk.org/jeps/471
153             if (unsafe != null) {
154                 final Unsafe finalUnsafe = unsafe;
155                 final Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
156                     @Override
157                     public Object run() {
158                         try {
159                             // Other methods like storeFence() and invokeCleaner() are tested for elsewhere.
160                             Class<? extends Unsafe> cls = finalUnsafe.getClass();
161                             cls.getDeclaredMethod(
162                                     "copyMemory", Object.class, long.class, Object.class, long.class, long.class);
163                             if (javaVersion() > 23) {
164                                 cls.getDeclaredMethod("objectFieldOffset", Field.class);
165                                 cls.getDeclaredMethod("staticFieldOffset", Field.class);
166                                 cls.getDeclaredMethod("staticFieldBase", Field.class);
167                                 cls.getDeclaredMethod("arrayBaseOffset", Class.class);
168                                 cls.getDeclaredMethod("arrayIndexScale", Class.class);
169                                 cls.getDeclaredMethod("allocateMemory", long.class);
170                                 cls.getDeclaredMethod("reallocateMemory", long.class, long.class);
171                                 cls.getDeclaredMethod("freeMemory", long.class);
172                                 cls.getDeclaredMethod("setMemory", long.class, long.class, byte.class);
173                                 cls.getDeclaredMethod("setMemory", Object.class, long.class, long.class, byte.class);
174                                 cls.getDeclaredMethod("getBoolean", Object.class, long.class);
175                                 cls.getDeclaredMethod("getByte", long.class);
176                                 cls.getDeclaredMethod("getByte", Object.class, long.class);
177                                 cls.getDeclaredMethod("getInt", long.class);
178                                 cls.getDeclaredMethod("getInt", Object.class, long.class);
179                                 cls.getDeclaredMethod("getLong", long.class);
180                                 cls.getDeclaredMethod("getLong", Object.class, long.class);
181                                 cls.getDeclaredMethod("putByte", long.class, byte.class);
182                                 cls.getDeclaredMethod("putByte", Object.class, long.class, byte.class);
183                                 cls.getDeclaredMethod("putInt", long.class, int.class);
184                                 cls.getDeclaredMethod("putInt", Object.class, long.class, int.class);
185                                 cls.getDeclaredMethod("putLong", long.class, long.class);
186                                 cls.getDeclaredMethod("putLong", Object.class, long.class, long.class);
187                                 cls.getDeclaredMethod("addressSize");
188                             }
189                             if (javaVersion() >= 23) {
190                                 // The following tests the methods are usable.
191                                 // Will throw UnsupportedOperationException if unsafe memory access is denied:
192                                 long address = finalUnsafe.allocateMemory(8);
193                                 finalUnsafe.putLong(address, 42);
194                                 finalUnsafe.freeMemory(address);
195                             }
196                             return null;
197                         } catch (UnsupportedOperationException e) {
198                             return e;
199                         } catch (NoSuchMethodException e) {
200                             return e;
201                         } catch (SecurityException e) {
202                             return e;
203                         }
204                     }
205                 });
206 
207                 if (maybeException == null) {
208                     logger.debug("sun.misc.Unsafe base methods: all available");
209                 } else {
210                     // Unsafe.copyMemory(Object, long, Object, long, long) unavailable.
211                     unsafe = null;
212                     unsafeUnavailabilityCause = (Throwable) maybeException;
213                     if (logger.isTraceEnabled()) {
214                         logger.debug("sun.misc.Unsafe method unavailable:", unsafeUnavailabilityCause);
215                     } else {
216                         logger.debug("sun.misc.Unsafe method unavailable: {}",
217                                 ((Throwable) maybeException).getMessage());
218                     }
219                 }
220             }
221 
222             // ensure Unsafe::storeFence to be available: jdk < 8 shouldn't have it
223             if (unsafe != null) {
224                 final Unsafe finalUnsafe = unsafe;
225                 final Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
226                     @Override
227                     public Object run() {
228                         try {
229                             finalUnsafe.getClass().getDeclaredMethod("storeFence");
230                             return null;
231                         } catch (NoSuchMethodException e) {
232                             return e;
233                         } catch (SecurityException e) {
234                             return e;
235                         }
236                     }
237                 });
238 
239                 if (maybeException == null) {
240                     logger.debug("sun.misc.Unsafe.storeFence: available");
241                     storeFenceAvailable = true;
242                 } else {
243                     storeFenceAvailable = false;
244                     // Unsafe.storeFence unavailable.
245                     if (logger.isTraceEnabled()) {
246                         logger.debug("sun.misc.Unsafe.storeFence: unavailable", (Throwable) maybeException);
247                     } else {
248                         logger.debug("sun.misc.Unsafe.storeFence: unavailable: {}",
249                                      ((Throwable) maybeException).getMessage());
250                     }
251                 }
252             }
253 
254             if (unsafe != null) {
255                 final Unsafe finalUnsafe = unsafe;
256 
257                 // attempt to access field Buffer#address
258                 final Object maybeAddressField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
259                     @Override
260                     public Object run() {
261                         try {
262                             final Field field = Buffer.class.getDeclaredField("address");
263                             // Use Unsafe to read value of the address field. This way it will not fail on JDK9+ which
264                             // will forbid changing the access level via reflection.
265                             final long offset = finalUnsafe.objectFieldOffset(field);
266                             final long address = finalUnsafe.getLong(direct, offset);
267 
268                             // if direct really is a direct buffer, address will be non-zero
269                             if (address == 0) {
270                                 return null;
271                             }
272                             return field;
273                         } catch (NoSuchFieldException e) {
274                             return e;
275                         } catch (SecurityException e) {
276                             return e;
277                         }
278                     }
279                 });
280 
281                 if (maybeAddressField instanceof Field) {
282                     addressField = (Field) maybeAddressField;
283                     logger.debug("java.nio.Buffer.address: available");
284                 } else {
285                     unsafeUnavailabilityCause = (Throwable) maybeAddressField;
286                     if (logger.isTraceEnabled()) {
287                         logger.debug("java.nio.Buffer.address: unavailable", (Throwable) maybeAddressField);
288                     } else {
289                         logger.debug("java.nio.Buffer.address: unavailable: {}",
290                                 ((Throwable) maybeAddressField).getMessage());
291                     }
292 
293                     // If we cannot access the address of a direct buffer, there's no point of using unsafe.
294                     // Let's just pretend unsafe is unavailable for overall simplicity.
295                     unsafe = null;
296                 }
297             }
298 
299             if (unsafe != null) {
300                 // There are assumptions made where ever BYTE_ARRAY_BASE_OFFSET is used (equals, hashCodeAscii, and
301                 // primitive accessors) that arrayIndexScale == 1, and results are undefined if this is not the case.
302                 long byteArrayIndexScale = unsafe.arrayIndexScale(byte[].class);
303                 if (byteArrayIndexScale != 1) {
304                     logger.debug("unsafe.arrayIndexScale is {} (expected: 1). Not using unsafe.", byteArrayIndexScale);
305                     unsafeUnavailabilityCause = new UnsupportedOperationException("Unexpected unsafe.arrayIndexScale");
306                     unsafe = null;
307                 }
308             }
309         }
310         UNSAFE_UNAVAILABILITY_CAUSE = unsafeUnavailabilityCause;
311         UNSAFE = unsafe;
312 
313         if (unsafe == null) {
314             ADDRESS_FIELD_OFFSET = -1;
315             BYTE_ARRAY_BASE_OFFSET = -1;
316             LONG_ARRAY_BASE_OFFSET = -1;
317             LONG_ARRAY_INDEX_SCALE = -1;
318             INT_ARRAY_BASE_OFFSET = -1;
319             INT_ARRAY_INDEX_SCALE = -1;
320             UNALIGNED = false;
321             BITS_MAX_DIRECT_MEMORY = -1;
322             DIRECT_BUFFER_CONSTRUCTOR = null;
323             ALLOCATE_ARRAY_METHOD = null;
324             STORE_FENCE_AVAILABLE = false;
325         } else {
326             Constructor<?> directBufferConstructor;
327             long address = -1;
328             try {
329                 final Object maybeDirectBufferConstructor =
330                         AccessController.doPrivileged(new PrivilegedAction<Object>() {
331                             @Override
332                             public Object run() {
333                                 try {
334                                     final Constructor<?> constructor = javaVersion() >= 21 ?
335                                             direct.getClass().getDeclaredConstructor(long.class, long.class) :
336                                             direct.getClass().getDeclaredConstructor(long.class, int.class);
337                                     Throwable cause = ReflectionUtil.trySetAccessible(constructor, true);
338                                     if (cause != null) {
339                                         return cause;
340                                     }
341                                     return constructor;
342                                 } catch (NoSuchMethodException e) {
343                                     return e;
344                                 } catch (SecurityException e) {
345                                     return e;
346                                 }
347                             }
348                         });
349 
350                 if (maybeDirectBufferConstructor instanceof Constructor<?>) {
351                     address = UNSAFE.allocateMemory(1);
352                     // try to use the constructor now
353                     try {
354                         ((Constructor<?>) maybeDirectBufferConstructor).newInstance(address, 1);
355                         directBufferConstructor = (Constructor<?>) maybeDirectBufferConstructor;
356                         logger.debug("direct buffer constructor: available");
357                     } catch (InstantiationException e) {
358                         directBufferConstructor = null;
359                     } catch (IllegalAccessException e) {
360                         directBufferConstructor = null;
361                     } catch (InvocationTargetException e) {
362                         directBufferConstructor = null;
363                     }
364                 } else {
365                     if (logger.isTraceEnabled()) {
366                         logger.debug("direct buffer constructor: unavailable",
367                                 (Throwable) maybeDirectBufferConstructor);
368                     } else {
369                         logger.debug("direct buffer constructor: unavailable: {}",
370                                 ((Throwable) maybeDirectBufferConstructor).getMessage());
371                     }
372                     directBufferConstructor = null;
373                 }
374             } finally {
375                 if (address != -1) {
376                     UNSAFE.freeMemory(address);
377                 }
378             }
379             DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor;
380             ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
381             BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
382             INT_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(int[].class);
383             INT_ARRAY_INDEX_SCALE = UNSAFE.arrayIndexScale(int[].class);
384             LONG_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(long[].class);
385             LONG_ARRAY_INDEX_SCALE = UNSAFE.arrayIndexScale(long[].class);
386             final boolean unaligned;
387             // using a known type to avoid loading new classes
388             final AtomicLong maybeMaxMemory = new AtomicLong(-1);
389             Object maybeUnaligned = AccessController.doPrivileged(new PrivilegedAction<Object>() {
390                 @Override
391                 public Object run() {
392                     try {
393                         Class<?> bitsClass =
394                                 Class.forName("java.nio.Bits", false, getSystemClassLoader());
395                         int version = javaVersion();
396                         if (unsafeStaticFieldOffsetSupported() && version >= 9) {
397                             // Java9/10 use all lowercase and later versions all uppercase.
398                             String fieldName = version >= 11? "MAX_MEMORY" : "maxMemory";
399                             // On Java9 and later we try to directly access the field as we can do this without
400                             // adjust the accessible levels.
401                             try {
402                                 Field maxMemoryField = bitsClass.getDeclaredField(fieldName);
403                                 if (maxMemoryField.getType() == long.class) {
404                                     long offset = UNSAFE.staticFieldOffset(maxMemoryField);
405                                     Object object = UNSAFE.staticFieldBase(maxMemoryField);
406                                     maybeMaxMemory.lazySet(UNSAFE.getLong(object, offset));
407                                 }
408                             } catch (Throwable ignore) {
409                                 // ignore if can't access
410                             }
411                             fieldName = version >= 11? "UNALIGNED" : "unaligned";
412                             try {
413                                 Field unalignedField = bitsClass.getDeclaredField(fieldName);
414                                 if (unalignedField.getType() == boolean.class) {
415                                     long offset = UNSAFE.staticFieldOffset(unalignedField);
416                                     Object object = UNSAFE.staticFieldBase(unalignedField);
417                                     return UNSAFE.getBoolean(object, offset);
418                                 }
419                                 // There is something unexpected stored in the field,
420                                 // let us fall-back and try to use a reflective method call as last resort.
421                             } catch (NoSuchFieldException ignore) {
422                                 // We did not find the field we expected, move on.
423                             }
424                         }
425                         Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned");
426                         Throwable cause = ReflectionUtil.trySetAccessible(unalignedMethod, true);
427                         if (cause != null) {
428                             return cause;
429                         }
430                         return unalignedMethod.invoke(null);
431                     } catch (NoSuchMethodException e) {
432                         return e;
433                     } catch (SecurityException e) {
434                         return e;
435                     } catch (IllegalAccessException e) {
436                         return e;
437                     } catch (ClassNotFoundException e) {
438                         return e;
439                     } catch (InvocationTargetException e) {
440                         return e;
441                     }
442                 }
443             });
444 
445             if (maybeUnaligned instanceof Boolean) {
446                 unaligned = (Boolean) maybeUnaligned;
447                 logger.debug("java.nio.Bits.unaligned: available, {}", unaligned);
448             } else {
449                 String arch = SystemPropertyUtil.get("os.arch", "");
450                 //noinspection DynamicRegexReplaceableByCompiledPattern
451                 unaligned = arch.matches("^(i[3-6]86|x86(_64)?|x64|amd64)$");
452                 Throwable t = (Throwable) maybeUnaligned;
453                 if (logger.isTraceEnabled()) {
454                     logger.debug("java.nio.Bits.unaligned: unavailable, {}", unaligned, t);
455                 } else {
456                     logger.debug("java.nio.Bits.unaligned: unavailable, {}, {}", unaligned, t.getMessage());
457                 }
458             }
459 
460             UNALIGNED = unaligned;
461             BITS_MAX_DIRECT_MEMORY = maybeMaxMemory.get() >= 0? maybeMaxMemory.get() : -1;
462 
463             if (javaVersion() >= 9) {
464                 Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
465                     @Override
466                     public Object run() {
467                         try {
468                             // Java9 has jdk.internal.misc.Unsafe and not all methods are propagated to
469                             // sun.misc.Unsafe
470                             Class<?> internalUnsafeClass = getClassLoader(PlatformDependent0.class)
471                                     .loadClass("jdk.internal.misc.Unsafe");
472                             Method method = internalUnsafeClass.getDeclaredMethod("getUnsafe");
473                             return method.invoke(null);
474                         } catch (Throwable e) {
475                             return e;
476                         }
477                     }
478                 });
479                 if (!(maybeException instanceof Throwable)) {
480                     internalUnsafe = maybeException;
481                     final Object finalInternalUnsafe = internalUnsafe;
482                     maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
483                         @Override
484                         public Object run() {
485                             try {
486                                 return finalInternalUnsafe.getClass().getDeclaredMethod(
487                                         "allocateUninitializedArray", Class.class, int.class);
488                             } catch (NoSuchMethodException e) {
489                                 return e;
490                             } catch (SecurityException e) {
491                                 return e;
492                             }
493                         }
494                     });
495 
496                     if (maybeException instanceof Method) {
497                         try {
498                             Method m = (Method) maybeException;
499                             byte[] bytes = (byte[]) m.invoke(finalInternalUnsafe, byte.class, 8);
500                             assert bytes.length == 8;
501                             allocateArrayMethod = m;
502                         } catch (IllegalAccessException e) {
503                             maybeException = e;
504                         } catch (InvocationTargetException e) {
505                             maybeException = e;
506                         }
507                     }
508                 }
509 
510                 if (maybeException instanceof Throwable) {
511                     if (logger.isTraceEnabled()) {
512                         logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable",
513                                 (Throwable) maybeException);
514                     } else {
515                         logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable: {}",
516                                 ((Throwable) maybeException).getMessage());
517                     }
518                 } else {
519                     logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): available");
520                 }
521             } else {
522                 logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable prior to Java9");
523             }
524             ALLOCATE_ARRAY_METHOD = allocateArrayMethod;
525             STORE_FENCE_AVAILABLE = storeFenceAvailable;
526         }
527 
528         if (javaVersion() > 9) {
529             ALIGN_SLICE = (Method) AccessController.doPrivileged(new PrivilegedAction<Object>() {
530                 @Override
531                 public Object run() {
532                     try {
533                         return ByteBuffer.class.getDeclaredMethod("alignedSlice", int.class);
534                     } catch (Exception e) {
535                         return null;
536                     }
537                 }
538             });
539         } else {
540             ALIGN_SLICE = null;
541         }
542 
543         INTERNAL_UNSAFE = internalUnsafe;
544 
545         logger.debug("java.nio.DirectByteBuffer.<init>(long, {int,long}): {}",
546                 DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
547     }
548 
549     private static Method getIsVirtualThreadMethod() {
550         try {
551             Method isVirtualMethod = Thread.class.getMethod("isVirtual");
552             // Call once to make sure the invocation works.
553             boolean isVirtual = (Boolean) isVirtualMethod.invoke(Thread.currentThread());
554             return isVirtualMethod;
555         } catch (Throwable e) {
556             if (logger.isTraceEnabled()) {
557                 logger.debug("Thread.isVirtual() is not available: ", e);
558             } else {
559                 logger.debug("Thread.isVirtual() is not available: ", e.getMessage());
560             }
561             return null;
562         }
563     }
564 
565     private static Class<?> getBaseVirtualThreadClass() {
566         try {
567             return Class.forName("java.lang.BaseVirtualThread", false, getSystemClassLoader());
568         } catch (Throwable e) {
569             if (logger.isTraceEnabled()) {
570                 logger.debug("java.lang.BaseVirtualThread is not available: ", e);
571             } else {
572                 logger.debug("java.lang.BaseVirtualThread is not available: ", e.getMessage());
573             }
574             return null;
575         }
576     }
577 
578     /**
579      * @param thread The thread to be checked.
580      * @return {@code true} if this {@link Thread} is a virtual thread, {@code false} otherwise.
581      */
582     static boolean isVirtualThread(Thread thread) {
583         // Quick exclusion:
584         if (thread == null || IS_VIRTUAL_THREAD_METHOD == null) {
585             return false;
586         }
587         // Try fast check:
588         if (BASE_VIRTUAL_THREAD_CLASS != null) {
589             return BASE_VIRTUAL_THREAD_CLASS.isInstance(thread);
590         }
591         if (thread instanceof FastThreadLocalThread) {
592             return false;
593         }
594         Class<?> clazz = thread.getClass();
595         // Common cases:
596         if (clazz == Thread.class || clazz.getSuperclass() == Thread.class) {
597             return false;
598         }
599         try {
600             return (Boolean) IS_VIRTUAL_THREAD_METHOD.invoke(thread);
601         } catch (Throwable t) {
602             // Should not happen.
603             if (t instanceof Error) {
604                 throw (Error) t;
605             }
606             throw new Error(t);
607         }
608     }
609 
610     private static boolean unsafeStaticFieldOffsetSupported() {
611         return !RUNNING_IN_NATIVE_IMAGE;
612     }
613 
614     static boolean isExplicitNoUnsafe() {
615         return EXPLICIT_NO_UNSAFE_CAUSE != null;
616     }
617 
618     private static Throwable explicitNoUnsafeCause0() {
619         boolean explicitProperty = SystemPropertyUtil.contains("io.netty.noUnsafe");
620         boolean noUnsafe = SystemPropertyUtil.getBoolean("io.netty.noUnsafe", false);
621         logger.debug("-Dio.netty.noUnsafe: {}", noUnsafe);
622 
623         // See JDK 23 JEP 471 https://openjdk.org/jeps/471 and sun.misc.Unsafe.beforeMemoryAccess() on JDK 23+.
624         // And JDK 24 JEP 498 https://openjdk.org/jeps/498, that enable warnings by default.
625         String reason = "io.netty.noUnsafe";
626         String unspecified = "<unspecified>";
627         String unsafeMemoryAccess = SystemPropertyUtil.get("sun.misc.unsafe.memory.access", unspecified);
628         if (!explicitProperty && unspecified.equals(unsafeMemoryAccess) && javaVersion() >= 24) {
629             reason = "io.netty.noUnsafe=true by default on Java 24+";
630             noUnsafe = true;
631         } else if (!("allow".equals(unsafeMemoryAccess) || unspecified.equals(unsafeMemoryAccess))) {
632             reason = "--sun-misc-unsafe-memory-access=" + unsafeMemoryAccess;
633             noUnsafe = true;
634         }
635 
636         if (noUnsafe) {
637             String msg = "sun.misc.Unsafe: unavailable (" + reason + ')';
638             logger.debug(msg);
639             return new UnsupportedOperationException(msg);
640         }
641 
642         // Legacy properties
643         String unsafePropName;
644         if (SystemPropertyUtil.contains("io.netty.tryUnsafe")) {
645             unsafePropName = "io.netty.tryUnsafe";
646         } else {
647             unsafePropName = "org.jboss.netty.tryUnsafe";
648         }
649 
650         if (!SystemPropertyUtil.getBoolean(unsafePropName, true)) {
651             String msg = "sun.misc.Unsafe: unavailable (" + unsafePropName + ')';
652             logger.debug(msg);
653             return new UnsupportedOperationException(msg);
654         }
655 
656         return null;
657     }
658 
659     static boolean isUnaligned() {
660         return UNALIGNED;
661     }
662 
663     /**
664      * Any value >= 0 should be considered as a valid max direct memory value.
665      */
666     static long bitsMaxDirectMemory() {
667         return BITS_MAX_DIRECT_MEMORY;
668     }
669 
670     static boolean hasUnsafe() {
671         return UNSAFE != null;
672     }
673 
674     static Throwable getUnsafeUnavailabilityCause() {
675         return UNSAFE_UNAVAILABILITY_CAUSE;
676     }
677 
678     static boolean unalignedAccess() {
679         return UNALIGNED;
680     }
681 
682     static void throwException(Throwable cause) {
683         // JVM has been observed to crash when passing a null argument. See https://github.com/netty/netty/issues/4131.
684         UNSAFE.throwException(checkNotNull(cause, "cause"));
685     }
686 
687     static boolean hasDirectBufferNoCleanerConstructor() {
688         return DIRECT_BUFFER_CONSTRUCTOR != null;
689     }
690 
691     static ByteBuffer reallocateDirectNoCleaner(ByteBuffer buffer, int capacity) {
692         return newDirectBuffer(UNSAFE.reallocateMemory(directBufferAddress(buffer), capacity), capacity);
693     }
694 
695     static ByteBuffer allocateDirectNoCleaner(int capacity) {
696         // Calling malloc with capacity of 0 may return a null ptr or a memory address that can be used.
697         // Just use 1 to make it safe to use in all cases:
698         // See: https://pubs.opengroup.org/onlinepubs/009695399/functions/malloc.html
699         return newDirectBuffer(UNSAFE.allocateMemory(Math.max(1, capacity)), capacity);
700     }
701 
702     static boolean hasAlignSliceMethod() {
703         return ALIGN_SLICE != null;
704     }
705 
706     static ByteBuffer alignSlice(ByteBuffer buffer, int alignment) {
707         try {
708             return (ByteBuffer) ALIGN_SLICE.invoke(buffer, alignment);
709         } catch (IllegalAccessException e) {
710             throw new Error(e);
711         } catch (InvocationTargetException e) {
712             throw new Error(e);
713         }
714     }
715 
716     static boolean hasAllocateArrayMethod() {
717         return ALLOCATE_ARRAY_METHOD != null;
718     }
719 
720     static byte[] allocateUninitializedArray(int size) {
721         try {
722             return (byte[]) ALLOCATE_ARRAY_METHOD.invoke(INTERNAL_UNSAFE, byte.class, size);
723         } catch (IllegalAccessException e) {
724             throw new Error(e);
725         } catch (InvocationTargetException e) {
726             throw new Error(e);
727         }
728     }
729 
730     static ByteBuffer newDirectBuffer(long address, int capacity) {
731         ObjectUtil.checkPositiveOrZero(capacity, "capacity");
732 
733         try {
734             return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity);
735         } catch (Throwable cause) {
736             // Not expected to ever throw!
737             if (cause instanceof Error) {
738                 throw (Error) cause;
739             }
740             throw new Error(cause);
741         }
742     }
743 
744     static long directBufferAddress(ByteBuffer buffer) {
745         return getLong(buffer, ADDRESS_FIELD_OFFSET);
746     }
747 
748     static long byteArrayBaseOffset() {
749         return BYTE_ARRAY_BASE_OFFSET;
750     }
751 
752     static Object getObject(Object object, long fieldOffset) {
753         return UNSAFE.getObject(object, fieldOffset);
754     }
755 
756     static int getInt(Object object, long fieldOffset) {
757         return UNSAFE.getInt(object, fieldOffset);
758     }
759 
760     static void safeConstructPutInt(Object object, long fieldOffset, int value) {
761         if (STORE_FENCE_AVAILABLE) {
762             UNSAFE.putInt(object, fieldOffset, value);
763             UNSAFE.storeFence();
764         } else {
765             UNSAFE.putIntVolatile(object, fieldOffset, value);
766         }
767     }
768 
769     private static long getLong(Object object, long fieldOffset) {
770         return UNSAFE.getLong(object, fieldOffset);
771     }
772 
773     static long objectFieldOffset(Field field) {
774         return UNSAFE.objectFieldOffset(field);
775     }
776 
777     static byte getByte(long address) {
778         return UNSAFE.getByte(address);
779     }
780 
781     static short getShort(long address) {
782         return UNSAFE.getShort(address);
783     }
784 
785     static int getInt(long address) {
786         return UNSAFE.getInt(address);
787     }
788 
789     static long getLong(long address) {
790         return UNSAFE.getLong(address);
791     }
792 
793     static byte getByte(byte[] data, int index) {
794         return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);
795     }
796 
797     static byte getByte(byte[] data, long index) {
798         return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);
799     }
800 
801     static short getShort(byte[] data, int index) {
802         return UNSAFE.getShort(data, BYTE_ARRAY_BASE_OFFSET + index);
803     }
804 
805     static int getInt(byte[] data, int index) {
806         return UNSAFE.getInt(data, BYTE_ARRAY_BASE_OFFSET + index);
807     }
808 
809     static int getInt(int[] data, long index) {
810         return UNSAFE.getInt(data, INT_ARRAY_BASE_OFFSET + INT_ARRAY_INDEX_SCALE * index);
811     }
812 
813     static int getIntVolatile(long address) {
814         return UNSAFE.getIntVolatile(null, address);
815     }
816 
817     static void putIntOrdered(long adddress, int newValue) {
818         UNSAFE.putOrderedInt(null, adddress, newValue);
819     }
820 
821     static long getLong(byte[] data, int index) {
822         return UNSAFE.getLong(data, BYTE_ARRAY_BASE_OFFSET + index);
823     }
824 
825     static long getLong(long[] data, long index) {
826         return UNSAFE.getLong(data, LONG_ARRAY_BASE_OFFSET + LONG_ARRAY_INDEX_SCALE * index);
827     }
828 
829     static void putByte(long address, byte value) {
830         UNSAFE.putByte(address, value);
831     }
832 
833     static void putShort(long address, short value) {
834         UNSAFE.putShort(address, value);
835     }
836 
837     static void putInt(long address, int value) {
838         UNSAFE.putInt(address, value);
839     }
840 
841     static void putLong(long address, long value) {
842         UNSAFE.putLong(address, value);
843     }
844 
845     static void putByte(byte[] data, int index, byte value) {
846         UNSAFE.putByte(data, BYTE_ARRAY_BASE_OFFSET + index, value);
847     }
848 
849     static void putByte(Object data, long offset, byte value) {
850         UNSAFE.putByte(data, offset, value);
851     }
852 
853     static void putShort(byte[] data, int index, short value) {
854         UNSAFE.putShort(data, BYTE_ARRAY_BASE_OFFSET + index, value);
855     }
856 
857     static void putInt(byte[] data, int index, int value) {
858         UNSAFE.putInt(data, BYTE_ARRAY_BASE_OFFSET + index, value);
859     }
860 
861     static void putLong(byte[] data, int index, long value) {
862         UNSAFE.putLong(data, BYTE_ARRAY_BASE_OFFSET + index, value);
863     }
864 
865     static void putObject(Object o, long offset, Object x) {
866         UNSAFE.putObject(o, offset, x);
867     }
868 
869     static void copyMemory(long srcAddr, long dstAddr, long length) {
870         // Manual safe-point polling is only needed prior Java9:
871         // See https://bugs.openjdk.java.net/browse/JDK-8149596
872         if (javaVersion() <= 8) {
873             copyMemoryWithSafePointPolling(srcAddr, dstAddr, length);
874         } else {
875             UNSAFE.copyMemory(srcAddr, dstAddr, length);
876         }
877     }
878 
879     private static void copyMemoryWithSafePointPolling(long srcAddr, long dstAddr, long length) {
880         while (length > 0) {
881             long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
882             UNSAFE.copyMemory(srcAddr, dstAddr, size);
883             length -= size;
884             srcAddr += size;
885             dstAddr += size;
886         }
887     }
888 
889     static void copyMemory(Object src, long srcOffset, Object dst, long dstOffset, long length) {
890         // Manual safe-point polling is only needed prior Java9:
891         // See https://bugs.openjdk.java.net/browse/JDK-8149596
892         if (javaVersion() <= 8) {
893             copyMemoryWithSafePointPolling(src, srcOffset, dst, dstOffset, length);
894         } else {
895             UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length);
896         }
897     }
898 
899     private static void copyMemoryWithSafePointPolling(
900             Object src, long srcOffset, Object dst, long dstOffset, long length) {
901         while (length > 0) {
902             long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
903             UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, size);
904             length -= size;
905             srcOffset += size;
906             dstOffset += size;
907         }
908     }
909 
910     static void setMemory(long address, long bytes, byte value) {
911         UNSAFE.setMemory(address, bytes, value);
912     }
913 
914     static void setMemory(Object o, long offset, long bytes, byte value) {
915         UNSAFE.setMemory(o, offset, bytes, value);
916     }
917 
918     static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
919         int remainingBytes = length & 7;
920         final long baseOffset1 = BYTE_ARRAY_BASE_OFFSET + startPos1;
921         final long diff = startPos2 - startPos1;
922         if (length >= 8) {
923             final long end = baseOffset1 + remainingBytes;
924             for (long i = baseOffset1 - 8 + length; i >= end; i -= 8) {
925                 if (UNSAFE.getLong(bytes1, i) != UNSAFE.getLong(bytes2, i + diff)) {
926                     return false;
927                 }
928             }
929         }
930         if (remainingBytes >= 4) {
931             remainingBytes -= 4;
932             long pos = baseOffset1 + remainingBytes;
933             if (UNSAFE.getInt(bytes1, pos) != UNSAFE.getInt(bytes2, pos + diff)) {
934                 return false;
935             }
936         }
937         final long baseOffset2 = baseOffset1 + diff;
938         if (remainingBytes >= 2) {
939             return UNSAFE.getChar(bytes1, baseOffset1) == UNSAFE.getChar(bytes2, baseOffset2) &&
940                     (remainingBytes == 2 ||
941                     UNSAFE.getByte(bytes1, baseOffset1 + 2) == UNSAFE.getByte(bytes2, baseOffset2 + 2));
942         }
943         return remainingBytes == 0 ||
944                 UNSAFE.getByte(bytes1, baseOffset1) == UNSAFE.getByte(bytes2, baseOffset2);
945     }
946 
947     static int equalsConstantTime(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
948         long result = 0;
949         long remainingBytes = length & 7;
950         final long baseOffset1 = BYTE_ARRAY_BASE_OFFSET + startPos1;
951         final long end = baseOffset1 + remainingBytes;
952         final long diff = startPos2 - startPos1;
953         for (long i = baseOffset1 - 8 + length; i >= end; i -= 8) {
954             result |= UNSAFE.getLong(bytes1, i) ^ UNSAFE.getLong(bytes2, i + diff);
955         }
956         if (remainingBytes >= 4) {
957             result |= UNSAFE.getInt(bytes1, baseOffset1) ^ UNSAFE.getInt(bytes2, baseOffset1 + diff);
958             remainingBytes -= 4;
959         }
960         if (remainingBytes >= 2) {
961             long pos = end - remainingBytes;
962             result |= UNSAFE.getChar(bytes1, pos) ^ UNSAFE.getChar(bytes2, pos + diff);
963             remainingBytes -= 2;
964         }
965         if (remainingBytes == 1) {
966             long pos = end - 1;
967             result |= UNSAFE.getByte(bytes1, pos) ^ UNSAFE.getByte(bytes2, pos + diff);
968         }
969         return ConstantTimeUtils.equalsConstantTime(result, 0);
970     }
971 
972     static boolean isZero(byte[] bytes, int startPos, int length) {
973         if (length <= 0) {
974             return true;
975         }
976         final long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos;
977         int remainingBytes = length & 7;
978         final long end = baseOffset + remainingBytes;
979         for (long i = baseOffset - 8 + length; i >= end; i -= 8) {
980             if (UNSAFE.getLong(bytes, i) != 0) {
981                 return false;
982             }
983         }
984 
985         if (remainingBytes >= 4) {
986             remainingBytes -= 4;
987             if (UNSAFE.getInt(bytes, baseOffset + remainingBytes) != 0) {
988                 return false;
989             }
990         }
991         if (remainingBytes >= 2) {
992             return UNSAFE.getChar(bytes, baseOffset) == 0 &&
993                     (remainingBytes == 2 || bytes[startPos + 2] == 0);
994         }
995         return bytes[startPos] == 0;
996     }
997 
998     static int hashCodeAscii(byte[] bytes, int startPos, int length) {
999         int hash = HASH_CODE_ASCII_SEED;
1000         long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos;
1001         final int remainingBytes = length & 7;
1002         final long end = baseOffset + remainingBytes;
1003         for (long i = baseOffset - 8 + length; i >= end; i -= 8) {
1004             hash = hashCodeAsciiCompute(UNSAFE.getLong(bytes, i), hash);
1005         }
1006         if (remainingBytes == 0) {
1007             return hash;
1008         }
1009         int hcConst = HASH_CODE_C1;
1010         if (remainingBytes != 2 & remainingBytes != 4 & remainingBytes != 6) { // 1, 3, 5, 7
1011             hash = hash * HASH_CODE_C1 + hashCodeAsciiSanitize(UNSAFE.getByte(bytes, baseOffset));
1012             hcConst = HASH_CODE_C2;
1013             baseOffset++;
1014         }
1015         if (remainingBytes != 1 & remainingBytes != 4 & remainingBytes != 5) { // 2, 3, 6, 7
1016             hash = hash * hcConst + hashCodeAsciiSanitize(UNSAFE.getShort(bytes, baseOffset));
1017             hcConst = hcConst == HASH_CODE_C1 ? HASH_CODE_C2 : HASH_CODE_C1;
1018             baseOffset += 2;
1019         }
1020         if (remainingBytes >= 4) { // 4, 5, 6, 7
1021             return hash * hcConst + hashCodeAsciiSanitize(UNSAFE.getInt(bytes, baseOffset));
1022         }
1023         return hash;
1024     }
1025 
1026     static int hashCodeAsciiCompute(long value, int hash) {
1027         // masking with 0x1f reduces the number of overall bits that impact the hash code but makes the hash
1028         // code the same regardless of character case (upper case or lower case hash is the same).
1029         return hash * HASH_CODE_C1 +
1030                 // Low order int
1031                 hashCodeAsciiSanitize((int) value) * HASH_CODE_C2 +
1032                 // High order int
1033                 (int) ((value & 0x1f1f1f1f00000000L) >>> 32);
1034     }
1035 
1036     static int hashCodeAsciiSanitize(int value) {
1037         return value & 0x1f1f1f1f;
1038     }
1039 
1040     static int hashCodeAsciiSanitize(short value) {
1041         return value & 0x1f1f;
1042     }
1043 
1044     static int hashCodeAsciiSanitize(byte value) {
1045         return value & 0x1f;
1046     }
1047 
1048     static ClassLoader getClassLoader(final Class<?> clazz) {
1049         if (System.getSecurityManager() == null) {
1050             return clazz.getClassLoader();
1051         } else {
1052             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
1053                 @Override
1054                 public ClassLoader run() {
1055                     return clazz.getClassLoader();
1056                 }
1057             });
1058         }
1059     }
1060 
1061     static ClassLoader getContextClassLoader() {
1062         if (System.getSecurityManager() == null) {
1063             return Thread.currentThread().getContextClassLoader();
1064         } else {
1065             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
1066                 @Override
1067                 public ClassLoader run() {
1068                     return Thread.currentThread().getContextClassLoader();
1069                 }
1070             });
1071         }
1072     }
1073 
1074     static ClassLoader getSystemClassLoader() {
1075         if (System.getSecurityManager() == null) {
1076             return ClassLoader.getSystemClassLoader();
1077         } else {
1078             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
1079                 @Override
1080                 public ClassLoader run() {
1081                     return ClassLoader.getSystemClassLoader();
1082                 }
1083             });
1084         }
1085     }
1086 
1087     static int addressSize() {
1088         return UNSAFE.addressSize();
1089     }
1090 
1091     static long allocateMemory(long size) {
1092         return UNSAFE.allocateMemory(size);
1093     }
1094 
1095     static void freeMemory(long address) {
1096         UNSAFE.freeMemory(address);
1097     }
1098 
1099     static long reallocateMemory(long address, long newSize) {
1100         return UNSAFE.reallocateMemory(address, newSize);
1101     }
1102 
1103     static boolean isAndroid() {
1104         return IS_ANDROID;
1105     }
1106 
1107     private static boolean isAndroid0() {
1108         // Idea: Sometimes java binaries include Android classes on the classpath, even if it isn't actually Android.
1109         // Rather than check if certain classes are present, just check the VM, which is tied to the JDK.
1110 
1111         // Optional improvement: check if `android.os.Build.VERSION` is >= 24. On later versions of Android, the
1112         // OpenJDK is used, which means `Unsafe` will actually work as expected.
1113 
1114         // Android sets this property to Dalvik, regardless of whether it actually is.
1115         String vmName = SystemPropertyUtil.get("java.vm.name");
1116         boolean isAndroid = "Dalvik".equals(vmName);
1117         if (isAndroid) {
1118             logger.debug("Platform: Android");
1119         }
1120         return isAndroid;
1121     }
1122 
1123     private static boolean explicitTryReflectionSetAccessible0() {
1124         // we disable reflective access
1125         return SystemPropertyUtil.getBoolean("io.netty.tryReflectionSetAccessible",
1126                 javaVersion() < 9 || RUNNING_IN_NATIVE_IMAGE);
1127     }
1128 
1129     static boolean isExplicitTryReflectionSetAccessible() {
1130         return IS_EXPLICIT_TRY_REFLECTION_SET_ACCESSIBLE;
1131     }
1132 
1133     static int javaVersion() {
1134         return JAVA_VERSION;
1135     }
1136 
1137     private static int javaVersion0() {
1138         final int majorVersion;
1139 
1140         if (isAndroid0()) {
1141             majorVersion = 6;
1142         } else {
1143             majorVersion = majorVersionFromJavaSpecificationVersion();
1144         }
1145 
1146         logger.debug("Java version: {}", majorVersion);
1147 
1148         return majorVersion;
1149     }
1150 
1151     // Package-private for testing only
1152     static int majorVersionFromJavaSpecificationVersion() {
1153         return majorVersion(SystemPropertyUtil.get("java.specification.version", "1.6"));
1154     }
1155 
1156     // Package-private for testing only
1157     static int majorVersion(final String javaSpecVersion) {
1158         final String[] components = javaSpecVersion.split("\\.");
1159         final int[] version = new int[components.length];
1160         for (int i = 0; i < components.length; i++) {
1161             version[i] = Integer.parseInt(components[i]);
1162         }
1163 
1164         if (version[0] == 1) {
1165             assert version[1] >= 6;
1166             return version[1];
1167         } else {
1168             return version[0];
1169         }
1170     }
1171 
1172     private PlatformDependent0() {
1173     }
1174 }