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