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    *   http://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.reflect.Constructor;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.nio.Buffer;
27  import java.nio.ByteBuffer;
28  import java.security.AccessController;
29  import java.security.PrivilegedAction;
30  
31  import static io.netty.util.internal.ObjectUtil.checkNotNull;
32  
33  /**
34   * The {@link PlatformDependent} operations which requires access to {@code sun.misc.*}.
35   */
36  final class PlatformDependent0 {
37  
38      private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent0.class);
39      private static final long ADDRESS_FIELD_OFFSET;
40      private static final long BYTE_ARRAY_BASE_OFFSET;
41      private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
42      private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0();
43      private static final Method ALLOCATE_ARRAY_METHOD;
44      private static final int JAVA_VERSION = javaVersion0();
45      private static final boolean IS_ANDROID = isAndroid0();
46  
47      private static final Throwable UNSAFE_UNAVAILABILITY_CAUSE;
48      private static final Object INTERNAL_UNSAFE;
49      static final Unsafe UNSAFE;
50  
51      /**
52       * Limits the number of bytes to copy per {@link Unsafe#copyMemory(long, long, long)} to allow safepoint polling
53       * during a large copy.
54       */
55      private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;
56  
57      private static final boolean UNALIGNED;
58  
59      static {
60          final ByteBuffer direct;
61          Field addressField = null;
62          Method allocateArrayMethod = null;
63          Throwable unsafeUnavailabilityCause = null;
64          Unsafe unsafe;
65          Object internalUnsafe = null;
66  
67          if (isExplicitNoUnsafe()) {
68              direct = null;
69              addressField = null;
70              unsafeUnavailabilityCause = new UnsupportedOperationException("Unsafe explicitly disabled");
71              unsafe = null;
72              internalUnsafe = null;
73          } else {
74              direct = ByteBuffer.allocateDirect(1);
75  
76              // attempt to access field Unsafe#theUnsafe
77              final Object maybeUnsafe = AccessController.doPrivileged(new PrivilegedAction<Object>() {
78                  @Override
79                  public Object run() {
80                      try {
81                          final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
82                          Throwable cause = ReflectionUtil.trySetAccessible(unsafeField);
83                          if (cause != null) {
84                              return cause;
85                          }
86                          // the unsafe instance
87                          return unsafeField.get(null);
88                      } catch (NoSuchFieldException e) {
89                          return e;
90                      } catch (SecurityException e) {
91                          return e;
92                      } catch (IllegalAccessException e) {
93                          return e;
94                      } catch (NoClassDefFoundError e) {
95                          // Also catch NoClassDefFoundError in case someone uses for example OSGI and it made
96                          // Unsafe unloadable.
97                          return e;
98                      }
99                  }
100             });
101 
102             // the conditional check here can not be replaced with checking that maybeUnsafe
103             // is an instanceof Unsafe and reversing the if and else blocks; this is because an
104             // instanceof check against Unsafe will trigger a class load and we might not have
105             // the runtime permission accessClassInPackage.sun.misc
106             if (maybeUnsafe instanceof Throwable) {
107                 unsafe = null;
108                 unsafeUnavailabilityCause = (Throwable) maybeUnsafe;
109                 logger.debug("sun.misc.Unsafe.theUnsafe: unavailable", (Throwable) maybeUnsafe);
110             } else {
111                 unsafe = (Unsafe) maybeUnsafe;
112                 logger.debug("sun.misc.Unsafe.theUnsafe: available");
113             }
114 
115             // ensure the unsafe supports all necessary methods to work around the mistake in the latest OpenJDK
116             // https://github.com/netty/netty/issues/1061
117             // http://www.mail-archive.com/[email protected]/msg00698.html
118             if (unsafe != null) {
119                 final Unsafe finalUnsafe = unsafe;
120                 final Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
121                     @Override
122                     public Object run() {
123                         try {
124                             finalUnsafe.getClass().getDeclaredMethod(
125                                     "copyMemory", Object.class, long.class, Object.class, long.class, long.class);
126                             return null;
127                         } catch (NoSuchMethodException e) {
128                             return e;
129                         } catch (SecurityException e) {
130                             return e;
131                         }
132                     }
133                 });
134 
135                 if (maybeException == null) {
136                     logger.debug("sun.misc.Unsafe.copyMemory: available");
137                 } else {
138                     // Unsafe.copyMemory(Object, long, Object, long, long) unavailable.
139                     unsafe = null;
140                     unsafeUnavailabilityCause = (Throwable) maybeException;
141                     logger.debug("sun.misc.Unsafe.copyMemory: unavailable", (Throwable) maybeException);
142                 }
143             }
144 
145             if (unsafe != null) {
146                 final Unsafe finalUnsafe = unsafe;
147 
148                 // attempt to access field Buffer#address
149                 final Object maybeAddressField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
150                     @Override
151                     public Object run() {
152                         try {
153                             final Field field = Buffer.class.getDeclaredField("address");
154                             // Use Unsafe to read value of the address field. This way it will not fail on JDK9+ which
155                             // will forbid changing the access level via reflection.
156                             final long offset = finalUnsafe.objectFieldOffset(field);
157                             final long address = finalUnsafe.getLong(direct, offset);
158 
159                             // if direct really is a direct buffer, address will be non-zero
160                             if (address == 0) {
161                                 return null;
162                             }
163                             return field;
164                         } catch (NoSuchFieldException e) {
165                             return e;
166                         } catch (SecurityException e) {
167                             return e;
168                         }
169                     }
170                 });
171 
172                 if (maybeAddressField instanceof Field) {
173                     addressField = (Field) maybeAddressField;
174                     logger.debug("java.nio.Buffer.address: available");
175                 } else {
176                     unsafeUnavailabilityCause = (Throwable) maybeAddressField;
177                     logger.debug("java.nio.Buffer.address: unavailable", (Throwable) maybeAddressField);
178 
179                     // If we cannot access the address of a direct buffer, there's no point of using unsafe.
180                     // Let's just pretend unsafe is unavailable for overall simplicity.
181                     unsafe = null;
182                 }
183             }
184 
185             if (unsafe != null) {
186                 // There are assumptions made where ever BYTE_ARRAY_BASE_OFFSET is used (equals, hashCodeAscii, and
187                 // primitive accessors) that arrayIndexScale == 1, and results are undefined if this is not the case.
188                 long byteArrayIndexScale = unsafe.arrayIndexScale(byte[].class);
189                 if (byteArrayIndexScale != 1) {
190                     logger.debug("unsafe.arrayIndexScale is {} (expected: 1). Not using unsafe.", byteArrayIndexScale);
191                     unsafeUnavailabilityCause = new UnsupportedOperationException("Unexpected unsafe.arrayIndexScale");
192                     unsafe = null;
193                 }
194             }
195         }
196         UNSAFE_UNAVAILABILITY_CAUSE = unsafeUnavailabilityCause;
197         UNSAFE = unsafe;
198 
199         if (unsafe == null) {
200             BYTE_ARRAY_BASE_OFFSET = -1;
201             ADDRESS_FIELD_OFFSET = -1;
202             UNALIGNED = false;
203             DIRECT_BUFFER_CONSTRUCTOR = null;
204             ALLOCATE_ARRAY_METHOD = null;
205         } else {
206             Constructor<?> directBufferConstructor;
207             long address = -1;
208             try {
209                 final Object maybeDirectBufferConstructor =
210                         AccessController.doPrivileged(new PrivilegedAction<Object>() {
211                             @Override
212                             public Object run() {
213                                 try {
214                                     final Constructor<?> constructor =
215                                             direct.getClass().getDeclaredConstructor(long.class, int.class);
216                                     Throwable cause = ReflectionUtil.trySetAccessible(constructor);
217                                     if (cause != null) {
218                                         return cause;
219                                     }
220                                     return constructor;
221                                 } catch (NoSuchMethodException e) {
222                                     return e;
223                                 } catch (SecurityException e) {
224                                     return e;
225                                 }
226                             }
227                         });
228 
229                 if (maybeDirectBufferConstructor instanceof Constructor<?>) {
230                     address = UNSAFE.allocateMemory(1);
231                     // try to use the constructor now
232                     try {
233                         ((Constructor<?>) maybeDirectBufferConstructor).newInstance(address, 1);
234                         directBufferConstructor = (Constructor<?>) maybeDirectBufferConstructor;
235                         logger.debug("direct buffer constructor: available");
236                     } catch (InstantiationException e) {
237                         directBufferConstructor = null;
238                     } catch (IllegalAccessException e) {
239                         directBufferConstructor = null;
240                     } catch (InvocationTargetException e) {
241                         directBufferConstructor = null;
242                     }
243                 } else {
244                     logger.debug(
245                             "direct buffer constructor: unavailable",
246                             (Throwable) maybeDirectBufferConstructor);
247                     directBufferConstructor = null;
248                 }
249             } finally {
250                 if (address != -1) {
251                     UNSAFE.freeMemory(address);
252                 }
253             }
254             DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor;
255             ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
256             boolean unaligned;
257             Object maybeUnaligned = AccessController.doPrivileged(new PrivilegedAction<Object>() {
258                 @Override
259                 public Object run() {
260                     try {
261                         Class<?> bitsClass =
262                                 Class.forName("java.nio.Bits", false, getSystemClassLoader());
263                         Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned");
264                         Throwable cause = ReflectionUtil.trySetAccessible(unalignedMethod);
265                         if (cause != null) {
266                             return cause;
267                         }
268                         return unalignedMethod.invoke(null);
269                     } catch (NoSuchMethodException e) {
270                         return e;
271                     } catch (SecurityException e) {
272                         return e;
273                     } catch (IllegalAccessException e) {
274                         return e;
275                     } catch (ClassNotFoundException e) {
276                         return e;
277                     } catch (InvocationTargetException e) {
278                         return e;
279                     }
280                 }
281             });
282 
283             if (maybeUnaligned instanceof Boolean) {
284                 unaligned = (Boolean) maybeUnaligned;
285                 logger.debug("java.nio.Bits.unaligned: available, {}", unaligned);
286             } else {
287                 String arch = SystemPropertyUtil.get("os.arch", "");
288                 //noinspection DynamicRegexReplaceableByCompiledPattern
289                 unaligned = arch.matches("^(i[3-6]86|x86(_64)?|x64|amd64)$");
290                 Throwable t = (Throwable) maybeUnaligned;
291                 logger.debug("java.nio.Bits.unaligned: unavailable {}", unaligned, t);
292             }
293 
294             UNALIGNED = unaligned;
295             BYTE_ARRAY_BASE_OFFSET = arrayBaseOffset();
296 
297             if (javaVersion() >= 9) {
298                 Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
299                     @Override
300                     public Object run() {
301                         try {
302                             // Java9 has jdk.internal.misc.Unsafe and not all methods are propagated to
303                             // sun.misc.Unsafe
304                             Class<?> internalUnsafeClass = getClassLoader(PlatformDependent0.class)
305                                     .loadClass("jdk.internal.misc.Unsafe");
306                             Method method = internalUnsafeClass.getDeclaredMethod("getUnsafe");
307                             return method.invoke(null);
308                         } catch (Throwable e) {
309                             return e;
310                         }
311                     }
312                 });
313                 if (!(maybeException instanceof Throwable)) {
314                     internalUnsafe = maybeException;
315                     final Object finalInternalUnsafe = internalUnsafe;
316                     maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
317                         @Override
318                         public Object run() {
319                             try {
320                                 return finalInternalUnsafe.getClass().getDeclaredMethod(
321                                         "allocateUninitializedArray", Class.class, int.class);
322                             } catch (NoSuchMethodException e) {
323                                 return e;
324                             } catch (SecurityException e) {
325                                 return e;
326                             }
327                         }
328                     });
329 
330                     if (maybeException instanceof Method) {
331                         try {
332                             Method m = (Method) maybeException;
333                             byte[] bytes = (byte[]) m.invoke(finalInternalUnsafe, byte.class, 8);
334                             assert bytes.length == 8;
335                             allocateArrayMethod = m;
336                         } catch (IllegalAccessException e) {
337                             maybeException = e;
338                         } catch (InvocationTargetException e) {
339                             maybeException = e;
340                         }
341                     }
342                 }
343 
344                 if (maybeException instanceof Throwable) {
345                     logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable",
346                             (Throwable) maybeException);
347                 } else {
348                     logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): available");
349                 }
350             } else {
351                 logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable prior to Java9");
352             }
353             ALLOCATE_ARRAY_METHOD = allocateArrayMethod;
354         }
355 
356         INTERNAL_UNSAFE = internalUnsafe;
357 
358         logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
359                 DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
360     }
361 
362     static boolean isExplicitNoUnsafe() {
363         return IS_EXPLICIT_NO_UNSAFE;
364     }
365 
366     private static boolean explicitNoUnsafe0() {
367         final boolean noUnsafe = SystemPropertyUtil.getBoolean("io.netty.noUnsafe", false);
368         logger.debug("-Dio.netty.noUnsafe: {}", noUnsafe);
369 
370         if (noUnsafe) {
371             logger.debug("sun.misc.Unsafe: unavailable (io.netty.noUnsafe)");
372             return true;
373         }
374 
375         // Legacy properties
376         boolean tryUnsafe;
377         if (SystemPropertyUtil.contains("io.netty.tryUnsafe")) {
378             tryUnsafe = SystemPropertyUtil.getBoolean("io.netty.tryUnsafe", true);
379         } else {
380             tryUnsafe = SystemPropertyUtil.getBoolean("org.jboss.netty.tryUnsafe", true);
381         }
382 
383         if (!tryUnsafe) {
384             logger.debug("sun.misc.Unsafe: unavailable (io.netty.tryUnsafe/org.jboss.netty.tryUnsafe)");
385             return true;
386         }
387 
388         return false;
389     }
390 
391     static boolean isUnaligned() {
392         return UNALIGNED;
393     }
394 
395     static boolean hasUnsafe() {
396         return UNSAFE != null;
397     }
398 
399     static Throwable getUnsafeUnavailabilityCause() {
400         return UNSAFE_UNAVAILABILITY_CAUSE;
401     }
402 
403     static void throwException(Throwable cause) {
404         // JVM has been observed to crash when passing a null argument. See https://github.com/netty/netty/issues/4131.
405         UNSAFE.throwException(checkNotNull(cause, "cause"));
406     }
407 
408     static boolean hasDirectBufferNoCleanerConstructor() {
409         return DIRECT_BUFFER_CONSTRUCTOR != null;
410     }
411 
412     static ByteBuffer reallocateDirectNoCleaner(ByteBuffer buffer, int capacity) {
413         return newDirectBuffer(UNSAFE.reallocateMemory(directBufferAddress(buffer), capacity), capacity);
414     }
415 
416     static ByteBuffer allocateDirectNoCleaner(int capacity) {
417         return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity);
418     }
419 
420     static boolean hasAllocateArrayMethod() {
421         return ALLOCATE_ARRAY_METHOD != null;
422     }
423 
424     static byte[] allocateUninitializedArray(int size) {
425         try {
426             return (byte[]) ALLOCATE_ARRAY_METHOD.invoke(INTERNAL_UNSAFE, byte.class, size);
427         } catch (IllegalAccessException e) {
428             throw new Error(e);
429         } catch (InvocationTargetException e) {
430             throw new Error(e);
431         }
432     }
433 
434     static ByteBuffer newDirectBuffer(long address, int capacity) {
435         ObjectUtil.checkPositiveOrZero(capacity, "capacity");
436 
437         try {
438             return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity);
439         } catch (Throwable cause) {
440             // Not expected to ever throw!
441             if (cause instanceof Error) {
442                 throw (Error) cause;
443             }
444             throw new Error(cause);
445         }
446     }
447 
448     static long directBufferAddress(ByteBuffer buffer) {
449         return getLong(buffer, ADDRESS_FIELD_OFFSET);
450     }
451 
452     static long arrayBaseOffset() {
453         return UNSAFE.arrayBaseOffset(byte[].class);
454     }
455 
456     static Object getObject(Object object, long fieldOffset) {
457         return UNSAFE.getObject(object, fieldOffset);
458     }
459 
460     static int getInt(Object object, long fieldOffset) {
461         return UNSAFE.getInt(object, fieldOffset);
462     }
463 
464     private static long getLong(Object object, long fieldOffset) {
465         return UNSAFE.getLong(object, fieldOffset);
466     }
467 
468     static long objectFieldOffset(Field field) {
469         return UNSAFE.objectFieldOffset(field);
470     }
471 
472     static byte getByte(long address) {
473         return UNSAFE.getByte(address);
474     }
475 
476     static short getShort(long address) {
477         return UNSAFE.getShort(address);
478     }
479 
480     static int getInt(long address) {
481         return UNSAFE.getInt(address);
482     }
483 
484     static long getLong(long address) {
485         return UNSAFE.getLong(address);
486     }
487 
488     static byte getByte(byte[] data, int index) {
489         return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);
490     }
491 
492     static short getShort(byte[] data, int index) {
493         return UNSAFE.getShort(data, BYTE_ARRAY_BASE_OFFSET + index);
494     }
495 
496     static int getInt(byte[] data, int index) {
497         return UNSAFE.getInt(data, BYTE_ARRAY_BASE_OFFSET + index);
498     }
499 
500     static long getLong(byte[] data, int index) {
501         return UNSAFE.getLong(data, BYTE_ARRAY_BASE_OFFSET + index);
502     }
503 
504     static void putByte(long address, byte value) {
505         UNSAFE.putByte(address, value);
506     }
507 
508     static void putShort(long address, short value) {
509         UNSAFE.putShort(address, value);
510     }
511 
512     static void putInt(long address, int value) {
513         UNSAFE.putInt(address, value);
514     }
515 
516     static void putLong(long address, long value) {
517         UNSAFE.putLong(address, value);
518     }
519 
520     static void putByte(byte[] data, int index, byte value) {
521         UNSAFE.putByte(data, BYTE_ARRAY_BASE_OFFSET + index, value);
522     }
523 
524     static void putShort(byte[] data, int index, short value) {
525         UNSAFE.putShort(data, BYTE_ARRAY_BASE_OFFSET + index, value);
526     }
527 
528     static void putInt(byte[] data, int index, int value) {
529         UNSAFE.putInt(data, BYTE_ARRAY_BASE_OFFSET + index, value);
530     }
531 
532     static void putLong(byte[] data, int index, long value) {
533         UNSAFE.putLong(data, BYTE_ARRAY_BASE_OFFSET + index, value);
534     }
535 
536     static void copyMemory(long srcAddr, long dstAddr, long length) {
537         //UNSAFE.copyMemory(srcAddr, dstAddr, length);
538         while (length > 0) {
539             long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
540             UNSAFE.copyMemory(srcAddr, dstAddr, size);
541             length -= size;
542             srcAddr += size;
543             dstAddr += size;
544         }
545     }
546 
547     static void copyMemory(Object src, long srcOffset, Object dst, long dstOffset, long length) {
548         //UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length);
549         while (length > 0) {
550             long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
551             UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, size);
552             length -= size;
553             srcOffset += size;
554             dstOffset += size;
555         }
556     }
557 
558     static void setMemory(long address, long bytes, byte value) {
559         UNSAFE.setMemory(address, bytes, value);
560     }
561 
562     static void setMemory(Object o, long offset, long bytes, byte value) {
563         UNSAFE.setMemory(o, offset, bytes, value);
564     }
565 
566     static boolean isZero(byte[] bytes, int startPos, int length) {
567         if (length <= 0) {
568             return true;
569         }
570         final long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos;
571         int remainingBytes = length & 7;
572         final long end = baseOffset + remainingBytes;
573         for (long i = baseOffset - 8 + length; i >= end; i -= 8) {
574             if (UNSAFE.getLong(bytes, i) != 0) {
575                 return false;
576             }
577         }
578 
579         if (remainingBytes >= 4) {
580             remainingBytes -= 4;
581             if (UNSAFE.getInt(bytes, baseOffset + remainingBytes) != 0) {
582                 return false;
583             }
584         }
585         if (remainingBytes >= 2) {
586             return UNSAFE.getChar(bytes, baseOffset) == 0 &&
587                     (remainingBytes == 2 || bytes[startPos + 2] == 0);
588         }
589         return bytes[startPos] == 0;
590     }
591 
592     static ClassLoader getClassLoader(final Class<?> clazz) {
593         if (System.getSecurityManager() == null) {
594             return clazz.getClassLoader();
595         } else {
596             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
597                 @Override
598                 public ClassLoader run() {
599                     return clazz.getClassLoader();
600                 }
601             });
602         }
603     }
604 
605     static ClassLoader getContextClassLoader() {
606         if (System.getSecurityManager() == null) {
607             return Thread.currentThread().getContextClassLoader();
608         } else {
609             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
610                 @Override
611                 public ClassLoader run() {
612                     return Thread.currentThread().getContextClassLoader();
613                 }
614             });
615         }
616     }
617 
618     static ClassLoader getSystemClassLoader() {
619         if (System.getSecurityManager() == null) {
620             return ClassLoader.getSystemClassLoader();
621         } else {
622             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
623                 @Override
624                 public ClassLoader run() {
625                     return ClassLoader.getSystemClassLoader();
626                 }
627             });
628         }
629     }
630 
631     static int addressSize() {
632         return UNSAFE.addressSize();
633     }
634 
635     static long allocateMemory(long size) {
636         return UNSAFE.allocateMemory(size);
637     }
638 
639     static void freeMemory(long address) {
640         UNSAFE.freeMemory(address);
641     }
642 
643     static long reallocateMemory(long address, long newSize) {
644         return UNSAFE.reallocateMemory(address, newSize);
645     }
646 
647     static boolean isAndroid() {
648         return IS_ANDROID;
649     }
650 
651     private static boolean isAndroid0() {
652         boolean android;
653         try {
654             Class.forName("android.app.Application", false, getSystemClassLoader());
655             android = true;
656         } catch (Throwable ignored) {
657             // Failed to load the class uniquely available in Android.
658             android = false;
659         }
660 
661         if (android) {
662             logger.debug("Platform: Android");
663         }
664         return android;
665     }
666 
667     static int javaVersion() {
668         return JAVA_VERSION;
669     }
670 
671     private static int javaVersion0() {
672         final int majorVersion;
673 
674         if (isAndroid0()) {
675             majorVersion = 6;
676         } else {
677             majorVersion = majorVersionFromJavaSpecificationVersion();
678         }
679 
680         logger.debug("Java version: {}", majorVersion);
681 
682         return majorVersion;
683     }
684 
685     // Package-private for testing only
686     static int majorVersionFromJavaSpecificationVersion() {
687         return majorVersion(SystemPropertyUtil.get("java.specification.version", "1.6"));
688     }
689 
690     // Package-private for testing only
691     static int majorVersion(final String javaSpecVersion) {
692         final String[] components = javaSpecVersion.split("\\.");
693         final int[] version = new int[components.length];
694         for (int i = 0; i < components.length; i++) {
695             version[i] = Integer.parseInt(components[i]);
696         }
697 
698         if (version[0] == 1) {
699             assert version[1] >= 6;
700             return version[1];
701         } else {
702             return version[0];
703         }
704     }
705 
706     private PlatformDependent0() {
707     }
708 
709 }