1375 lines
53 KiB
Java
1375 lines
53 KiB
Java
/*
|
|
* Copyright 2012 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
package io.netty.util.internal;
|
|
|
|
import io.netty.util.CharsetUtil;
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
import org.jctools.queues.MpscArrayQueue;
|
|
import org.jctools.queues.MpscChunkedArrayQueue;
|
|
import org.jctools.queues.MpscUnboundedArrayQueue;
|
|
import org.jctools.queues.SpscLinkedQueue;
|
|
import org.jctools.queues.atomic.MpscAtomicArrayQueue;
|
|
import org.jctools.queues.atomic.MpscGrowableAtomicArrayQueue;
|
|
import org.jctools.queues.atomic.MpscUnboundedAtomicArrayQueue;
|
|
import org.jctools.queues.atomic.SpscLinkedAtomicQueue;
|
|
import org.jctools.util.Pow2;
|
|
import org.jctools.util.UnsafeAccess;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.security.AccessController;
|
|
import java.security.PrivilegedAction;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Deque;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Queue;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import static io.netty.util.internal.PlatformDependent0.HASH_CODE_ASCII_SEED;
|
|
import static io.netty.util.internal.PlatformDependent0.HASH_CODE_C1;
|
|
import static io.netty.util.internal.PlatformDependent0.HASH_CODE_C2;
|
|
import static io.netty.util.internal.PlatformDependent0.hashCodeAsciiSanitize;
|
|
import static io.netty.util.internal.PlatformDependent0.unalignedAccess;
|
|
import static java.lang.Math.max;
|
|
import static java.lang.Math.min;
|
|
|
|
/**
|
|
* Utility that detects various properties specific to the current runtime
|
|
* environment, such as Java version and the availability of the
|
|
* {@code sun.misc.Unsafe} object.
|
|
* <p>
|
|
* You can disable the use of {@code sun.misc.Unsafe} if you specify
|
|
* the system property <strong>io.netty.noUnsafe</strong>.
|
|
*/
|
|
public final class PlatformDependent {
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent.class);
|
|
|
|
private static final Pattern MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN = Pattern.compile(
|
|
"\\s*-XX:MaxDirectMemorySize\\s*=\\s*([0-9]+)\\s*([kKmMgG]?)\\s*$");
|
|
|
|
private static final boolean IS_WINDOWS = isWindows0();
|
|
private static final boolean IS_OSX = isOsx0();
|
|
private static final boolean IS_J9_JVM = isJ9Jvm0();
|
|
private static final boolean IS_IVKVM_DOT_NET = isIkvmDotNet0();
|
|
|
|
private static final boolean MAYBE_SUPER_USER;
|
|
|
|
private static final boolean CAN_ENABLE_TCP_NODELAY_BY_DEFAULT = !isAndroid();
|
|
|
|
private static final Throwable UNSAFE_UNAVAILABILITY_CAUSE = unsafeUnavailabilityCause0();
|
|
private static final boolean DIRECT_BUFFER_PREFERRED;
|
|
private static final long MAX_DIRECT_MEMORY = maxDirectMemory0();
|
|
|
|
private static final int MPSC_CHUNK_SIZE = 1024;
|
|
private static final int MIN_MAX_MPSC_CAPACITY = MPSC_CHUNK_SIZE * 2;
|
|
private static final int MAX_ALLOWED_MPSC_CAPACITY = Pow2.MAX_POW2;
|
|
|
|
private static final long BYTE_ARRAY_BASE_OFFSET = byteArrayBaseOffset0();
|
|
|
|
private static final File TMPDIR = tmpdir0();
|
|
|
|
private static final int BIT_MODE = bitMode0();
|
|
private static final String NORMALIZED_ARCH = normalizeArch(SystemPropertyUtil.get("os.arch", ""));
|
|
private static final String NORMALIZED_OS = normalizeOs(SystemPropertyUtil.get("os.name", ""));
|
|
|
|
// keep in sync with maven's pom.xml via os.detection.classifierWithLikes!
|
|
private static final String[] ALLOWED_LINUX_OS_CLASSIFIERS = {"fedora", "suse", "arch"};
|
|
private static final Set<String> LINUX_OS_CLASSIFIERS;
|
|
|
|
private static final int ADDRESS_SIZE = addressSize0();
|
|
private static final boolean USE_DIRECT_BUFFER_NO_CLEANER;
|
|
private static final AtomicLong DIRECT_MEMORY_COUNTER;
|
|
private static final long DIRECT_MEMORY_LIMIT;
|
|
private static final Cleaner CLEANER;
|
|
private static final int UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD;
|
|
// For specifications, see https://www.freedesktop.org/software/systemd/man/os-release.html
|
|
private static final String[] OS_RELEASE_FILES = {"/etc/os-release", "/usr/lib/os-release"};
|
|
private static final String LINUX_ID_PREFIX = "ID=";
|
|
private static final String LINUX_ID_LIKE_PREFIX = "ID_LIKE=";
|
|
public static final boolean BIG_ENDIAN_NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
|
|
|
private static final Cleaner NOOP = buffer -> {
|
|
// NOOP
|
|
};
|
|
|
|
static {
|
|
// Here is how the system property is used:
|
|
//
|
|
// * < 0 - Don't use cleaner, and inherit max direct memory from java. In this case the
|
|
// "practical max direct memory" would be 2 * max memory as defined by the JDK.
|
|
// * == 0 - Use cleaner, Netty will not enforce max memory, and instead will defer to JDK.
|
|
// * > 0 - Don't use cleaner. This will limit Netty's total direct memory
|
|
// (note: that JDK's direct memory limit is independent of this).
|
|
long maxDirectMemory = SystemPropertyUtil.getLong("io.netty.maxDirectMemory", -1);
|
|
|
|
if (maxDirectMemory == 0 || !hasUnsafe() || !PlatformDependent0.hasDirectBufferNoCleanerConstructor()) {
|
|
USE_DIRECT_BUFFER_NO_CLEANER = false;
|
|
DIRECT_MEMORY_COUNTER = null;
|
|
} else {
|
|
USE_DIRECT_BUFFER_NO_CLEANER = true;
|
|
if (maxDirectMemory < 0) {
|
|
maxDirectMemory = MAX_DIRECT_MEMORY;
|
|
if (maxDirectMemory <= 0) {
|
|
DIRECT_MEMORY_COUNTER = null;
|
|
} else {
|
|
DIRECT_MEMORY_COUNTER = new AtomicLong();
|
|
}
|
|
} else {
|
|
DIRECT_MEMORY_COUNTER = new AtomicLong();
|
|
}
|
|
}
|
|
logger.debug("-Dio.netty.maxDirectMemory: {} bytes", maxDirectMemory);
|
|
DIRECT_MEMORY_LIMIT = maxDirectMemory >= 1 ? maxDirectMemory : MAX_DIRECT_MEMORY;
|
|
|
|
int tryAllocateUninitializedArray =
|
|
SystemPropertyUtil.getInt("io.netty.uninitializedArrayAllocationThreshold", 1024);
|
|
UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD = javaVersion() >= 9 && PlatformDependent0.hasAllocateArrayMethod() ?
|
|
tryAllocateUninitializedArray : -1;
|
|
logger.debug("-Dio.netty.uninitializedArrayAllocationThreshold: {}", UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD);
|
|
|
|
MAYBE_SUPER_USER = maybeSuperUser0();
|
|
|
|
if (!isAndroid()) {
|
|
// only direct to method if we are not running on android.
|
|
// See https://github.com/netty/netty/issues/2604
|
|
if (javaVersion() >= 9) {
|
|
CLEANER = CleanerJava9.isSupported() ? new CleanerJava9() : NOOP;
|
|
} else {
|
|
CLEANER = CleanerJava6.isSupported() ? new CleanerJava6() : NOOP;
|
|
}
|
|
} else {
|
|
CLEANER = NOOP;
|
|
}
|
|
|
|
// We should always prefer direct buffers by default if we can use a Cleaner to release direct buffers.
|
|
DIRECT_BUFFER_PREFERRED = CLEANER != NOOP
|
|
&& !SystemPropertyUtil.getBoolean("io.netty.noPreferDirect", false);
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug("-Dio.netty.noPreferDirect: {}", !DIRECT_BUFFER_PREFERRED);
|
|
}
|
|
|
|
/*
|
|
* We do not want to log this message if unsafe is explicitly disabled. Do not remove the explicit no unsafe
|
|
* guard.
|
|
*/
|
|
if (CLEANER == NOOP && !PlatformDependent0.isExplicitNoUnsafe()) {
|
|
logger.info(
|
|
"Your platform does not provide complete low-level API for accessing direct buffers reliably. " +
|
|
"Unless explicitly requested, heap buffer will always be preferred to avoid potential system " +
|
|
"instability.");
|
|
}
|
|
|
|
final Set<String> allowedClassifiers = Collections.unmodifiableSet(
|
|
new HashSet<String>(Arrays.asList(ALLOWED_LINUX_OS_CLASSIFIERS)));
|
|
final Set<String> availableClassifiers = new LinkedHashSet<String>();
|
|
for (final String osReleaseFileName : OS_RELEASE_FILES) {
|
|
final File file = new File(osReleaseFileName);
|
|
boolean found = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
|
|
try {
|
|
if (file.exists()) {
|
|
BufferedReader reader = null;
|
|
try {
|
|
reader = new BufferedReader(
|
|
new InputStreamReader(
|
|
new FileInputStream(file), CharsetUtil.UTF_8));
|
|
|
|
String line;
|
|
while ((line = reader.readLine()) != null) {
|
|
if (line.startsWith(LINUX_ID_PREFIX)) {
|
|
String id = normalizeOsReleaseVariableValue(
|
|
line.substring(LINUX_ID_PREFIX.length()));
|
|
addClassifier(allowedClassifiers, availableClassifiers, id);
|
|
} else if (line.startsWith(LINUX_ID_LIKE_PREFIX)) {
|
|
line = normalizeOsReleaseVariableValue(
|
|
line.substring(LINUX_ID_LIKE_PREFIX.length()));
|
|
addClassifier(allowedClassifiers, availableClassifiers, line.split("[ ]+"));
|
|
}
|
|
}
|
|
} catch (SecurityException e) {
|
|
logger.debug("Unable to read {}", osReleaseFileName, e);
|
|
} catch (IOException e) {
|
|
logger.debug("Error while reading content of {}", osReleaseFileName, e);
|
|
} finally {
|
|
if (reader != null) {
|
|
try {
|
|
reader.close();
|
|
} catch (IOException ignored) {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|
|
// specification states we should only fall back if /etc/os-release does not exist
|
|
return true;
|
|
}
|
|
} catch (SecurityException e) {
|
|
logger.debug("Unable to check if {} exists", osReleaseFileName, e);
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (found) {
|
|
break;
|
|
}
|
|
}
|
|
LINUX_OS_CLASSIFIERS = Collections.unmodifiableSet(availableClassifiers);
|
|
}
|
|
|
|
public static boolean hasDirectBufferNoCleanerConstructor() {
|
|
return PlatformDependent0.hasDirectBufferNoCleanerConstructor();
|
|
}
|
|
|
|
public static byte[] allocateUninitializedArray(int size) {
|
|
return UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD < 0 || UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD > size ?
|
|
new byte[size] : PlatformDependent0.allocateUninitializedArray(size);
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if and only if the current platform is Android
|
|
*/
|
|
public static boolean isAndroid() {
|
|
return PlatformDependent0.isAndroid();
|
|
}
|
|
|
|
/**
|
|
* Return {@code true} if the JVM is running on Windows
|
|
*/
|
|
public static boolean isWindows() {
|
|
return IS_WINDOWS;
|
|
}
|
|
|
|
/**
|
|
* Return {@code true} if the JVM is running on OSX / MacOS
|
|
*/
|
|
public static boolean isOsx() {
|
|
return IS_OSX;
|
|
}
|
|
|
|
/**
|
|
* Return {@code true} if the current user may be a super-user. Be aware that this is just an hint and so it may
|
|
* return false-positives.
|
|
*/
|
|
public static boolean maybeSuperUser() {
|
|
return MAYBE_SUPER_USER;
|
|
}
|
|
|
|
/**
|
|
* Return the version of Java under which this library is used.
|
|
*/
|
|
public static int javaVersion() {
|
|
return PlatformDependent0.javaVersion();
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if and only if it is fine to enable TCP_NODELAY socket option by default.
|
|
*/
|
|
public static boolean canEnableTcpNoDelayByDefault() {
|
|
return CAN_ENABLE_TCP_NODELAY_BY_DEFAULT;
|
|
}
|
|
|
|
/**
|
|
* Return {@code true} if {@code sun.misc.Unsafe} was found on the classpath and can be used for accelerated
|
|
* direct memory access.
|
|
*/
|
|
public static boolean hasUnsafe() {
|
|
return UNSAFE_UNAVAILABILITY_CAUSE == null;
|
|
}
|
|
|
|
/**
|
|
* Return the reason (if any) why {@code sun.misc.Unsafe} was not available.
|
|
*/
|
|
public static Throwable getUnsafeUnavailabilityCause() {
|
|
return UNSAFE_UNAVAILABILITY_CAUSE;
|
|
}
|
|
|
|
/**
|
|
* {@code true} if and only if the platform supports unaligned access.
|
|
*
|
|
* @see <a href="http://en.wikipedia.org/wiki/Segmentation_fault#Bus_error">Wikipedia on segfault</a>
|
|
*/
|
|
public static boolean isUnaligned() {
|
|
return PlatformDependent0.isUnaligned();
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if the platform has reliable low-level direct buffer access API and a user has not specified
|
|
* {@code -Dio.netty.noPreferDirect} option.
|
|
*/
|
|
public static boolean directBufferPreferred() {
|
|
return DIRECT_BUFFER_PREFERRED;
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum memory reserved for direct buffer allocation.
|
|
*/
|
|
public static long maxDirectMemory() {
|
|
return DIRECT_MEMORY_LIMIT;
|
|
}
|
|
|
|
/**
|
|
* Returns the current memory reserved for direct buffer allocation.
|
|
* This method returns -1 in case that a value is not available.
|
|
*
|
|
* @see #maxDirectMemory()
|
|
*/
|
|
public static long usedDirectMemory() {
|
|
return DIRECT_MEMORY_COUNTER != null ? DIRECT_MEMORY_COUNTER.get() : -1;
|
|
}
|
|
|
|
/**
|
|
* Returns the temporary directory.
|
|
*/
|
|
public static File tmpdir() {
|
|
return TMPDIR;
|
|
}
|
|
|
|
/**
|
|
* Returns the bit mode of the current VM (usually 32 or 64.)
|
|
*/
|
|
public static int bitMode() {
|
|
return BIT_MODE;
|
|
}
|
|
|
|
/**
|
|
* Return the address size of the OS.
|
|
* 4 (for 32 bits systems ) and 8 (for 64 bits systems).
|
|
*/
|
|
public static int addressSize() {
|
|
return ADDRESS_SIZE;
|
|
}
|
|
|
|
public static long allocateMemory(long size) {
|
|
return PlatformDependent0.allocateMemory(size);
|
|
}
|
|
|
|
public static void freeMemory(long address) {
|
|
PlatformDependent0.freeMemory(address);
|
|
}
|
|
|
|
public static long reallocateMemory(long address, long newSize) {
|
|
return PlatformDependent0.reallocateMemory(address, newSize);
|
|
}
|
|
|
|
/**
|
|
* Raises an exception bypassing compiler checks for checked exceptions.
|
|
*/
|
|
public static void throwException(Throwable t) {
|
|
if (hasUnsafe()) {
|
|
PlatformDependent0.throwException(t);
|
|
} else {
|
|
PlatformDependent.throwException0(t);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static <E extends Throwable> void throwException0(Throwable t) throws E {
|
|
throw (E) t;
|
|
}
|
|
|
|
/**
|
|
* Try to deallocate the specified direct {@link ByteBuffer}. Please note this method does nothing if
|
|
* the current platform does not support this operation or the specified buffer is not a direct buffer.
|
|
*/
|
|
public static void freeDirectBuffer(ByteBuffer buffer) {
|
|
CLEANER.freeDirectBuffer(buffer);
|
|
}
|
|
|
|
public static long directBufferAddress(ByteBuffer buffer) {
|
|
return PlatformDependent0.directBufferAddress(buffer);
|
|
}
|
|
|
|
public static ByteBuffer directBuffer(long memoryAddress, int size) {
|
|
if (PlatformDependent0.hasDirectBufferNoCleanerConstructor()) {
|
|
return PlatformDependent0.newDirectBuffer(memoryAddress, size);
|
|
}
|
|
throw new UnsupportedOperationException(
|
|
"sun.misc.Unsafe or java.nio.DirectByteBuffer.<init>(long, int) not available");
|
|
}
|
|
|
|
public static Object getObject(Object object, long fieldOffset) {
|
|
return PlatformDependent0.getObject(object, fieldOffset);
|
|
}
|
|
|
|
public static int getInt(Object object, long fieldOffset) {
|
|
return PlatformDependent0.getInt(object, fieldOffset);
|
|
}
|
|
|
|
public static byte getByte(long address) {
|
|
return PlatformDependent0.getByte(address);
|
|
}
|
|
|
|
public static short getShort(long address) {
|
|
return PlatformDependent0.getShort(address);
|
|
}
|
|
|
|
public static int getInt(long address) {
|
|
return PlatformDependent0.getInt(address);
|
|
}
|
|
|
|
public static long getLong(long address) {
|
|
return PlatformDependent0.getLong(address);
|
|
}
|
|
|
|
public static byte getByte(byte[] data, int index) {
|
|
return PlatformDependent0.getByte(data, index);
|
|
}
|
|
|
|
public static short getShort(byte[] data, int index) {
|
|
return PlatformDependent0.getShort(data, index);
|
|
}
|
|
|
|
public static int getInt(byte[] data, int index) {
|
|
return PlatformDependent0.getInt(data, index);
|
|
}
|
|
|
|
public static long getLong(byte[] data, int index) {
|
|
return PlatformDependent0.getLong(data, index);
|
|
}
|
|
|
|
private static long getLongSafe(byte[] bytes, int offset) {
|
|
if (BIG_ENDIAN_NATIVE_ORDER) {
|
|
return (long) bytes[offset] << 56 |
|
|
((long) bytes[offset + 1] & 0xff) << 48 |
|
|
((long) bytes[offset + 2] & 0xff) << 40 |
|
|
((long) bytes[offset + 3] & 0xff) << 32 |
|
|
((long) bytes[offset + 4] & 0xff) << 24 |
|
|
((long) bytes[offset + 5] & 0xff) << 16 |
|
|
((long) bytes[offset + 6] & 0xff) << 8 |
|
|
(long) bytes[offset + 7] & 0xff;
|
|
}
|
|
return (long) bytes[offset] & 0xff |
|
|
((long) bytes[offset + 1] & 0xff) << 8 |
|
|
((long) bytes[offset + 2] & 0xff) << 16 |
|
|
((long) bytes[offset + 3] & 0xff) << 24 |
|
|
((long) bytes[offset + 4] & 0xff) << 32 |
|
|
((long) bytes[offset + 5] & 0xff) << 40 |
|
|
((long) bytes[offset + 6] & 0xff) << 48 |
|
|
(long) bytes[offset + 7] << 56;
|
|
}
|
|
|
|
private static int getIntSafe(byte[] bytes, int offset) {
|
|
if (BIG_ENDIAN_NATIVE_ORDER) {
|
|
return bytes[offset] << 24 |
|
|
(bytes[offset + 1] & 0xff) << 16 |
|
|
(bytes[offset + 2] & 0xff) << 8 |
|
|
bytes[offset + 3] & 0xff;
|
|
}
|
|
return bytes[offset] & 0xff |
|
|
(bytes[offset + 1] & 0xff) << 8 |
|
|
(bytes[offset + 2] & 0xff) << 16 |
|
|
bytes[offset + 3] << 24;
|
|
}
|
|
|
|
private static short getShortSafe(byte[] bytes, int offset) {
|
|
if (BIG_ENDIAN_NATIVE_ORDER) {
|
|
return (short) (bytes[offset] << 8 | (bytes[offset + 1] & 0xff));
|
|
}
|
|
return (short) (bytes[offset] & 0xff | (bytes[offset + 1] << 8));
|
|
}
|
|
|
|
/**
|
|
* Identical to {@link PlatformDependent0#hashCodeAsciiCompute(long, int)} but for {@link CharSequence}.
|
|
*/
|
|
private static int hashCodeAsciiCompute(CharSequence value, int offset, int hash) {
|
|
if (BIG_ENDIAN_NATIVE_ORDER) {
|
|
return hash * HASH_CODE_C1 +
|
|
// Low order int
|
|
hashCodeAsciiSanitizeInt(value, offset + 4) * HASH_CODE_C2 +
|
|
// High order int
|
|
hashCodeAsciiSanitizeInt(value, offset);
|
|
}
|
|
return hash * HASH_CODE_C1 +
|
|
// Low order int
|
|
hashCodeAsciiSanitizeInt(value, offset) * HASH_CODE_C2 +
|
|
// High order int
|
|
hashCodeAsciiSanitizeInt(value, offset + 4);
|
|
}
|
|
|
|
/**
|
|
* Identical to {@link PlatformDependent0#hashCodeAsciiSanitize(int)} but for {@link CharSequence}.
|
|
*/
|
|
private static int hashCodeAsciiSanitizeInt(CharSequence value, int offset) {
|
|
if (BIG_ENDIAN_NATIVE_ORDER) {
|
|
// mimic a unsafe.getInt call on a big endian machine
|
|
return (value.charAt(offset + 3) & 0x1f) |
|
|
(value.charAt(offset + 2) & 0x1f) << 8 |
|
|
(value.charAt(offset + 1) & 0x1f) << 16 |
|
|
(value.charAt(offset) & 0x1f) << 24;
|
|
}
|
|
return (value.charAt(offset + 3) & 0x1f) << 24 |
|
|
(value.charAt(offset + 2) & 0x1f) << 16 |
|
|
(value.charAt(offset + 1) & 0x1f) << 8 |
|
|
(value.charAt(offset) & 0x1f);
|
|
}
|
|
|
|
/**
|
|
* Identical to {@link PlatformDependent0#hashCodeAsciiSanitize(short)} but for {@link CharSequence}.
|
|
*/
|
|
private static int hashCodeAsciiSanitizeShort(CharSequence value, int offset) {
|
|
if (BIG_ENDIAN_NATIVE_ORDER) {
|
|
// mimic a unsafe.getShort call on a big endian machine
|
|
return (value.charAt(offset + 1) & 0x1f) |
|
|
(value.charAt(offset) & 0x1f) << 8;
|
|
}
|
|
return (value.charAt(offset + 1) & 0x1f) << 8 |
|
|
(value.charAt(offset) & 0x1f);
|
|
}
|
|
|
|
/**
|
|
* Identical to {@link PlatformDependent0#hashCodeAsciiSanitize(byte)} but for {@link CharSequence}.
|
|
*/
|
|
private static int hashCodeAsciiSanitizeByte(char value) {
|
|
return value & 0x1f;
|
|
}
|
|
|
|
public static void putByte(long address, byte value) {
|
|
PlatformDependent0.putByte(address, value);
|
|
}
|
|
|
|
public static void putShort(long address, short value) {
|
|
PlatformDependent0.putShort(address, value);
|
|
}
|
|
|
|
public static void putInt(long address, int value) {
|
|
PlatformDependent0.putInt(address, value);
|
|
}
|
|
|
|
public static void putLong(long address, long value) {
|
|
PlatformDependent0.putLong(address, value);
|
|
}
|
|
|
|
public static void putByte(byte[] data, int index, byte value) {
|
|
PlatformDependent0.putByte(data, index, value);
|
|
}
|
|
|
|
public static void putShort(byte[] data, int index, short value) {
|
|
PlatformDependent0.putShort(data, index, value);
|
|
}
|
|
|
|
public static void putInt(byte[] data, int index, int value) {
|
|
PlatformDependent0.putInt(data, index, value);
|
|
}
|
|
|
|
public static void putLong(byte[] data, int index, long value) {
|
|
PlatformDependent0.putLong(data, index, value);
|
|
}
|
|
|
|
public static void putObject(Object o, long offset, Object x) {
|
|
PlatformDependent0.putObject(o, offset, x);
|
|
}
|
|
|
|
public static long objectFieldOffset(Field field) {
|
|
return PlatformDependent0.objectFieldOffset(field);
|
|
}
|
|
|
|
public static void copyMemory(long srcAddr, long dstAddr, long length) {
|
|
PlatformDependent0.copyMemory(srcAddr, dstAddr, length);
|
|
}
|
|
|
|
public static void copyMemory(byte[] src, int srcIndex, long dstAddr, long length) {
|
|
PlatformDependent0.copyMemory(src, BYTE_ARRAY_BASE_OFFSET + srcIndex, null, dstAddr, length);
|
|
}
|
|
|
|
public static void copyMemory(long srcAddr, byte[] dst, int dstIndex, long length) {
|
|
PlatformDependent0.copyMemory(null, srcAddr, dst, BYTE_ARRAY_BASE_OFFSET + dstIndex, length);
|
|
}
|
|
|
|
public static void setMemory(byte[] dst, int dstIndex, long bytes, byte value) {
|
|
PlatformDependent0.setMemory(dst, BYTE_ARRAY_BASE_OFFSET + dstIndex, bytes, value);
|
|
}
|
|
|
|
public static void setMemory(long address, long bytes, byte value) {
|
|
PlatformDependent0.setMemory(address, bytes, value);
|
|
}
|
|
|
|
/**
|
|
* Allocate a new {@link ByteBuffer} with the given {@code capacity}. {@link ByteBuffer}s allocated with
|
|
* this method <strong>MUST</strong> be deallocated via {@link #freeDirectNoCleaner(ByteBuffer)}.
|
|
*/
|
|
public static ByteBuffer allocateDirectNoCleaner(int capacity) {
|
|
assert USE_DIRECT_BUFFER_NO_CLEANER;
|
|
|
|
incrementMemoryCounter(capacity);
|
|
try {
|
|
return PlatformDependent0.allocateDirectNoCleaner(capacity);
|
|
} catch (Throwable e) {
|
|
decrementMemoryCounter(capacity);
|
|
throwException(e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reallocate a new {@link ByteBuffer} with the given {@code capacity}. {@link ByteBuffer}s reallocated with
|
|
* this method <strong>MUST</strong> be deallocated via {@link #freeDirectNoCleaner(ByteBuffer)}.
|
|
*/
|
|
public static ByteBuffer reallocateDirectNoCleaner(ByteBuffer buffer, int capacity) {
|
|
assert USE_DIRECT_BUFFER_NO_CLEANER;
|
|
|
|
int len = capacity - buffer.capacity();
|
|
incrementMemoryCounter(len);
|
|
try {
|
|
return PlatformDependent0.reallocateDirectNoCleaner(buffer, capacity);
|
|
} catch (Throwable e) {
|
|
decrementMemoryCounter(len);
|
|
throwException(e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method <strong>MUST</strong> only be called for {@link ByteBuffer}s that were allocated via
|
|
* {@link #allocateDirectNoCleaner(int)}.
|
|
*/
|
|
public static void freeDirectNoCleaner(ByteBuffer buffer) {
|
|
assert USE_DIRECT_BUFFER_NO_CLEANER;
|
|
|
|
int capacity = buffer.capacity();
|
|
PlatformDependent0.freeMemory(PlatformDependent0.directBufferAddress(buffer));
|
|
decrementMemoryCounter(capacity);
|
|
}
|
|
|
|
private static void incrementMemoryCounter(int capacity) {
|
|
if (DIRECT_MEMORY_COUNTER != null) {
|
|
long newUsedMemory = DIRECT_MEMORY_COUNTER.addAndGet(capacity);
|
|
if (newUsedMemory > DIRECT_MEMORY_LIMIT) {
|
|
DIRECT_MEMORY_COUNTER.addAndGet(-capacity);
|
|
throw new OutOfDirectMemoryError("failed to allocate " + capacity
|
|
+ " byte(s) of direct memory (used: " + (newUsedMemory - capacity)
|
|
+ ", max: " + DIRECT_MEMORY_LIMIT + ')');
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void decrementMemoryCounter(int capacity) {
|
|
if (DIRECT_MEMORY_COUNTER != null) {
|
|
long usedMemory = DIRECT_MEMORY_COUNTER.addAndGet(-capacity);
|
|
assert usedMemory >= 0;
|
|
}
|
|
}
|
|
|
|
public static boolean useDirectBufferNoCleaner() {
|
|
return USE_DIRECT_BUFFER_NO_CLEANER;
|
|
}
|
|
|
|
/**
|
|
* Compare two {@code byte} arrays for equality. For performance reasons no bounds checking on the
|
|
* parameters is performed.
|
|
*
|
|
* @param bytes1 the first byte array.
|
|
* @param startPos1 the position (inclusive) to start comparing in {@code bytes1}.
|
|
* @param bytes2 the second byte array.
|
|
* @param startPos2 the position (inclusive) to start comparing in {@code bytes2}.
|
|
* @param length the amount of bytes to compare. This is assumed to be validated as not going out of bounds
|
|
* by the caller.
|
|
*/
|
|
public static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
|
|
return !hasUnsafe() || !unalignedAccess() ?
|
|
equalsSafe(bytes1, startPos1, bytes2, startPos2, length) :
|
|
PlatformDependent0.equals(bytes1, startPos1, bytes2, startPos2, length);
|
|
}
|
|
|
|
/**
|
|
* Determine if a subsection of an array is zero.
|
|
* @param bytes The byte array.
|
|
* @param startPos The starting index (inclusive) in {@code bytes}.
|
|
* @param length The amount of bytes to check for zero.
|
|
* @return {@code false} if {@code bytes[startPos:startsPos+length)} contains a value other than zero.
|
|
*/
|
|
public static boolean isZero(byte[] bytes, int startPos, int length) {
|
|
return !hasUnsafe() || !unalignedAccess() ?
|
|
isZeroSafe(bytes, startPos, length) :
|
|
PlatformDependent0.isZero(bytes, startPos, length);
|
|
}
|
|
|
|
/**
|
|
* Compare two {@code byte} arrays for equality without leaking timing information.
|
|
* For performance reasons no bounds checking on the parameters is performed.
|
|
* <p>
|
|
* The {@code int} return type is intentional and is designed to allow cascading of constant time operations:
|
|
* <pre>
|
|
* byte[] s1 = new {1, 2, 3};
|
|
* byte[] s2 = new {1, 2, 3};
|
|
* byte[] s3 = new {1, 2, 3};
|
|
* byte[] s4 = new {4, 5, 6};
|
|
* boolean equals = (equalsConstantTime(s1, 0, s2, 0, s1.length) &
|
|
* equalsConstantTime(s3, 0, s4, 0, s3.length)) != 0;
|
|
* </pre>
|
|
* @param bytes1 the first byte array.
|
|
* @param startPos1 the position (inclusive) to start comparing in {@code bytes1}.
|
|
* @param bytes2 the second byte array.
|
|
* @param startPos2 the position (inclusive) to start comparing in {@code bytes2}.
|
|
* @param length the amount of bytes to compare. This is assumed to be validated as not going out of bounds
|
|
* by the caller.
|
|
* @return {@code 0} if not equal. {@code 1} if equal.
|
|
*/
|
|
public static int equalsConstantTime(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
|
|
return !hasUnsafe() || !unalignedAccess() ?
|
|
ConstantTimeUtils.equalsConstantTime(bytes1, startPos1, bytes2, startPos2, length) :
|
|
PlatformDependent0.equalsConstantTime(bytes1, startPos1, bytes2, startPos2, length);
|
|
}
|
|
|
|
/**
|
|
* Calculate a hash code of a byte array assuming ASCII character encoding.
|
|
* The resulting hash code will be case insensitive.
|
|
* @param bytes The array which contains the data to hash.
|
|
* @param startPos What index to start generating a hash code in {@code bytes}
|
|
* @param length The amount of bytes that should be accounted for in the computation.
|
|
* @return The hash code of {@code bytes} assuming ASCII character encoding.
|
|
* The resulting hash code will be case insensitive.
|
|
*/
|
|
public static int hashCodeAscii(byte[] bytes, int startPos, int length) {
|
|
return !hasUnsafe() || !unalignedAccess() ?
|
|
hashCodeAsciiSafe(bytes, startPos, length) :
|
|
PlatformDependent0.hashCodeAscii(bytes, startPos, length);
|
|
}
|
|
|
|
/**
|
|
* Calculate a hash code of a byte array assuming ASCII character encoding.
|
|
* The resulting hash code will be case insensitive.
|
|
* <p>
|
|
* This method assumes that {@code bytes} is equivalent to a {@code byte[]} but just using {@link CharSequence}
|
|
* for storage. The upper most byte of each {@code char} from {@code bytes} is ignored.
|
|
* @param bytes The array which contains the data to hash (assumed to be equivalent to a {@code byte[]}).
|
|
* @return The hash code of {@code bytes} assuming ASCII character encoding.
|
|
* The resulting hash code will be case insensitive.
|
|
*/
|
|
public static int hashCodeAscii(CharSequence bytes) {
|
|
final int length = bytes.length();
|
|
final int remainingBytes = length & 7;
|
|
int hash = HASH_CODE_ASCII_SEED;
|
|
// Benchmarking shows that by just naively looping for inputs 8~31 bytes long we incur a relatively large
|
|
// performance penalty (only achieve about 60% performance of loop which iterates over each char). So because
|
|
// of this we take special provisions to unroll the looping for these conditions.
|
|
if (length >= 32) {
|
|
for (int i = length - 8; i >= remainingBytes; i -= 8) {
|
|
hash = hashCodeAsciiCompute(bytes, i, hash);
|
|
}
|
|
} else if (length >= 8) {
|
|
hash = hashCodeAsciiCompute(bytes, length - 8, hash);
|
|
if (length >= 16) {
|
|
hash = hashCodeAsciiCompute(bytes, length - 16, hash);
|
|
if (length >= 24) {
|
|
hash = hashCodeAsciiCompute(bytes, length - 24, hash);
|
|
}
|
|
}
|
|
}
|
|
if (remainingBytes == 0) {
|
|
return hash;
|
|
}
|
|
int offset = 0;
|
|
if (remainingBytes != 2 & remainingBytes != 4 & remainingBytes != 6) { // 1, 3, 5, 7
|
|
hash = hash * HASH_CODE_C1 + hashCodeAsciiSanitizeByte(bytes.charAt(0));
|
|
offset = 1;
|
|
}
|
|
if (remainingBytes != 1 & remainingBytes != 4 & remainingBytes != 5) { // 2, 3, 6, 7
|
|
hash = hash * (offset == 0 ? HASH_CODE_C1 : HASH_CODE_C2)
|
|
+ hashCodeAsciiSanitize(hashCodeAsciiSanitizeShort(bytes, offset));
|
|
offset += 2;
|
|
}
|
|
if (remainingBytes >= 4) { // 4, 5, 6, 7
|
|
return hash * ((offset == 0 | offset == 3) ? HASH_CODE_C1 : HASH_CODE_C2)
|
|
+ hashCodeAsciiSanitizeInt(bytes, offset);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
private static final class Mpsc {
|
|
private static final boolean USE_MPSC_CHUNKED_ARRAY_QUEUE;
|
|
|
|
private Mpsc() {
|
|
}
|
|
|
|
static {
|
|
Object unsafe = null;
|
|
if (hasUnsafe()) {
|
|
// jctools goes through its own process of initializing unsafe; of
|
|
// course, this requires permissions which might not be granted to calling code, so we
|
|
// must mark this block as privileged too
|
|
unsafe = AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
|
|
// force JCTools to initialize unsafe
|
|
return UnsafeAccess.UNSAFE;
|
|
});
|
|
}
|
|
|
|
if (unsafe == null) {
|
|
logger.debug("org.jctools-core.MpscChunkedArrayQueue: unavailable");
|
|
USE_MPSC_CHUNKED_ARRAY_QUEUE = false;
|
|
} else {
|
|
logger.debug("org.jctools-core.MpscChunkedArrayQueue: available");
|
|
USE_MPSC_CHUNKED_ARRAY_QUEUE = true;
|
|
}
|
|
}
|
|
|
|
static <T> Queue<T> newMpscQueue(final int maxCapacity) {
|
|
// Calculate the max capacity which can not be bigger then MAX_ALLOWED_MPSC_CAPACITY.
|
|
// This is forced by the MpscChunkedArrayQueue implementation as will try to round it
|
|
// up to the next power of two and so will overflow otherwise.
|
|
final int capacity = max(min(maxCapacity, MAX_ALLOWED_MPSC_CAPACITY), MIN_MAX_MPSC_CAPACITY);
|
|
return USE_MPSC_CHUNKED_ARRAY_QUEUE ? new MpscChunkedArrayQueue<>(MPSC_CHUNK_SIZE, capacity)
|
|
: new MpscGrowableAtomicArrayQueue<>(MPSC_CHUNK_SIZE, capacity);
|
|
}
|
|
|
|
static <T> Queue<T> newMpscQueue() {
|
|
return USE_MPSC_CHUNKED_ARRAY_QUEUE ? new MpscUnboundedArrayQueue<>(MPSC_CHUNK_SIZE)
|
|
: new MpscUnboundedAtomicArrayQueue<>(MPSC_CHUNK_SIZE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link Queue} which is safe to use for multiple producers (different threads) and a single
|
|
* consumer (one thread!).
|
|
* @return A MPSC queue which may be unbounded.
|
|
*/
|
|
public static <T> Queue<T> newMpscQueue() {
|
|
return Mpsc.newMpscQueue();
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link Queue} which is safe to use for multiple producers (different threads) and a single
|
|
* consumer (one thread!).
|
|
*/
|
|
public static <T> Queue<T> newMpscQueue(final int maxCapacity) {
|
|
return Mpsc.newMpscQueue(maxCapacity);
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link Queue} which is safe to use for single producer (one thread!) and a single
|
|
* consumer (one thread!).
|
|
*/
|
|
public static <T> Queue<T> newSpscQueue() {
|
|
return hasUnsafe() ? new SpscLinkedQueue<>() : new SpscLinkedAtomicQueue<>();
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link Queue} which is safe to use for multiple producers (different threads) and a single
|
|
* consumer (one thread!) with the given fixes {@code capacity}.
|
|
*/
|
|
public static <T> Queue<T> newFixedMpscQueue(int capacity) {
|
|
return hasUnsafe() ? new MpscArrayQueue<>(capacity) : new MpscAtomicArrayQueue<>(capacity);
|
|
}
|
|
|
|
/**
|
|
* Return the {@link ClassLoader} for the given {@link Class}.
|
|
*/
|
|
public static ClassLoader getClassLoader(final Class<?> clazz) {
|
|
return PlatformDependent0.getClassLoader(clazz);
|
|
}
|
|
|
|
/**
|
|
* Return the context {@link ClassLoader} for the current {@link Thread}.
|
|
*/
|
|
public static ClassLoader getContextClassLoader() {
|
|
return PlatformDependent0.getContextClassLoader();
|
|
}
|
|
|
|
/**
|
|
* Return the system {@link ClassLoader}.
|
|
*/
|
|
public static ClassLoader getSystemClassLoader() {
|
|
return PlatformDependent0.getSystemClassLoader();
|
|
}
|
|
|
|
/**
|
|
* Returns a new concurrent {@link Deque}.
|
|
*/
|
|
public static <C> Deque<C> newConcurrentDeque() {
|
|
return new ConcurrentLinkedDeque<>();
|
|
}
|
|
|
|
private static boolean isWindows0() {
|
|
boolean windows = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US).contains("win");
|
|
if (windows) {
|
|
logger.debug("Platform: Windows");
|
|
}
|
|
return windows;
|
|
}
|
|
|
|
private static boolean isOsx0() {
|
|
String osname = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US)
|
|
.replaceAll("[^a-z0-9]+", "");
|
|
boolean osx = osname.startsWith("macosx") || osname.startsWith("osx");
|
|
|
|
if (osx) {
|
|
logger.debug("Platform: MacOS");
|
|
}
|
|
return osx;
|
|
}
|
|
|
|
private static boolean maybeSuperUser0() {
|
|
String username = SystemPropertyUtil.get("user.name");
|
|
if (isWindows()) {
|
|
return "Administrator".equals(username);
|
|
}
|
|
// Check for root and toor as some BSDs have a toor user that is basically the same as root.
|
|
return "root".equals(username) || "toor".equals(username);
|
|
}
|
|
|
|
private static Throwable unsafeUnavailabilityCause0() {
|
|
if (isAndroid()) {
|
|
logger.debug("sun.misc.Unsafe: unavailable (Android)");
|
|
return new UnsupportedOperationException("sun.misc.Unsafe: unavailable (Android)");
|
|
}
|
|
|
|
if (isIkvmDotNet()) {
|
|
logger.debug("sun.misc.Unsafe: unavailable (IKVM.NET)");
|
|
return new UnsupportedOperationException("sun.misc.Unsafe: unavailable (IKVM.NET)");
|
|
}
|
|
|
|
Throwable cause = PlatformDependent0.getUnsafeUnavailabilityCause();
|
|
if (cause != null) {
|
|
return cause;
|
|
}
|
|
|
|
try {
|
|
boolean hasUnsafe = PlatformDependent0.hasUnsafe();
|
|
logger.debug("sun.misc.Unsafe: {}", hasUnsafe ? "available" : "unavailable");
|
|
return hasUnsafe ? null : PlatformDependent0.getUnsafeUnavailabilityCause();
|
|
} catch (Throwable t) {
|
|
logger.trace("Could not determine if Unsafe is available", t);
|
|
// Probably failed to initialize PlatformDependent0.
|
|
return new UnsupportedOperationException("Could not determine if Unsafe is available", t);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if the running JVM is either <a href="https://developer.ibm.com/javasdk/">IBM J9</a> or
|
|
* <a href="https://www.eclipse.org/openj9/">Eclipse OpenJ9</a>, {@code false} otherwise.
|
|
*/
|
|
public static boolean isJ9Jvm() {
|
|
return IS_J9_JVM;
|
|
}
|
|
|
|
private static boolean isJ9Jvm0() {
|
|
String vmName = SystemPropertyUtil.get("java.vm.name", "").toLowerCase();
|
|
return vmName.startsWith("ibm j9") || vmName.startsWith("eclipse openj9");
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if the running JVM is <a href="https://www.ikvm.net">IKVM.NET</a>, {@code false} otherwise.
|
|
*/
|
|
public static boolean isIkvmDotNet() {
|
|
return IS_IVKVM_DOT_NET;
|
|
}
|
|
|
|
private static boolean isIkvmDotNet0() {
|
|
String vmName = SystemPropertyUtil.get("java.vm.name", "").toUpperCase(Locale.US);
|
|
return vmName.equals("IKVM.NET");
|
|
}
|
|
|
|
private static long maxDirectMemory0() {
|
|
long maxDirectMemory = 0;
|
|
|
|
ClassLoader systemClassLoader = null;
|
|
try {
|
|
systemClassLoader = getSystemClassLoader();
|
|
|
|
// When using IBM J9 / Eclipse OpenJ9 we should not use VM.maxDirectMemory() as it not reflects the
|
|
// correct value.
|
|
// See:
|
|
// - https://github.com/netty/netty/issues/7654
|
|
String vmName = SystemPropertyUtil.get("java.vm.name", "").toLowerCase();
|
|
if (!vmName.startsWith("ibm j9") &&
|
|
// https://github.com/eclipse/openj9/blob/openj9-0.8.0/runtime/include/vendor_version.h#L53
|
|
!vmName.startsWith("eclipse openj9")) {
|
|
// Try to get from sun.misc.VM.maxDirectMemory() which should be most accurate.
|
|
Class<?> vmClass = Class.forName("sun.misc.VM", true, systemClassLoader);
|
|
Method m = vmClass.getDeclaredMethod("maxDirectMemory");
|
|
maxDirectMemory = ((Number) m.invoke(null)).longValue();
|
|
}
|
|
} catch (Throwable ignored) {
|
|
// Ignore
|
|
}
|
|
|
|
if (maxDirectMemory > 0) {
|
|
return maxDirectMemory;
|
|
}
|
|
|
|
try {
|
|
// Now try to get the JVM option (-XX:MaxDirectMemorySize) and parse it.
|
|
// Note that we are using reflection because Android doesn't have these classes.
|
|
Class<?> mgmtFactoryClass = Class.forName(
|
|
"java.lang.management.ManagementFactory", true, systemClassLoader);
|
|
Class<?> runtimeClass = Class.forName(
|
|
"java.lang.management.RuntimeMXBean", true, systemClassLoader);
|
|
|
|
Object runtime = mgmtFactoryClass.getDeclaredMethod("getRuntimeMXBean").invoke(null);
|
|
|
|
@SuppressWarnings("unchecked")
|
|
List<String> vmArgs = (List<String>) runtimeClass.getDeclaredMethod("getInputArguments").invoke(runtime);
|
|
for (int i = vmArgs.size() - 1; i >= 0; i --) {
|
|
Matcher m = MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN.matcher(vmArgs.get(i));
|
|
if (!m.matches()) {
|
|
continue;
|
|
}
|
|
|
|
maxDirectMemory = Long.parseLong(m.group(1));
|
|
switch (m.group(2).charAt(0)) {
|
|
case 'k': case 'K':
|
|
maxDirectMemory *= 1024;
|
|
break;
|
|
case 'm': case 'M':
|
|
maxDirectMemory *= 1024 * 1024;
|
|
break;
|
|
case 'g': case 'G':
|
|
maxDirectMemory *= 1024 * 1024 * 1024;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
} catch (Throwable ignored) {
|
|
// Ignore
|
|
}
|
|
|
|
if (maxDirectMemory <= 0) {
|
|
maxDirectMemory = Runtime.getRuntime().maxMemory();
|
|
logger.debug("maxDirectMemory: {} bytes (maybe)", maxDirectMemory);
|
|
} else {
|
|
logger.debug("maxDirectMemory: {} bytes", maxDirectMemory);
|
|
}
|
|
|
|
return maxDirectMemory;
|
|
}
|
|
|
|
private static File tmpdir0() {
|
|
File f;
|
|
try {
|
|
f = toDirectory(SystemPropertyUtil.get("io.netty.tmpdir"));
|
|
if (f != null) {
|
|
logger.debug("-Dio.netty.tmpdir: {}", f);
|
|
return f;
|
|
}
|
|
|
|
f = toDirectory(SystemPropertyUtil.get("java.io.tmpdir"));
|
|
if (f != null) {
|
|
logger.debug("-Dio.netty.tmpdir: {} (java.io.tmpdir)", f);
|
|
return f;
|
|
}
|
|
|
|
// This shouldn't happen, but just in case ..
|
|
if (isWindows()) {
|
|
f = toDirectory(System.getenv("TEMP"));
|
|
if (f != null) {
|
|
logger.debug("-Dio.netty.tmpdir: {} (%TEMP%)", f);
|
|
return f;
|
|
}
|
|
|
|
String userprofile = System.getenv("USERPROFILE");
|
|
if (userprofile != null) {
|
|
f = toDirectory(userprofile + "\\AppData\\Local\\Temp");
|
|
if (f != null) {
|
|
logger.debug("-Dio.netty.tmpdir: {} (%USERPROFILE%\\AppData\\Local\\Temp)", f);
|
|
return f;
|
|
}
|
|
|
|
f = toDirectory(userprofile + "\\Local Settings\\Temp");
|
|
if (f != null) {
|
|
logger.debug("-Dio.netty.tmpdir: {} (%USERPROFILE%\\Local Settings\\Temp)", f);
|
|
return f;
|
|
}
|
|
}
|
|
} else {
|
|
f = toDirectory(System.getenv("TMPDIR"));
|
|
if (f != null) {
|
|
logger.debug("-Dio.netty.tmpdir: {} ($TMPDIR)", f);
|
|
return f;
|
|
}
|
|
}
|
|
} catch (Throwable ignored) {
|
|
// Environment variable inaccessible
|
|
}
|
|
|
|
// Last resort.
|
|
if (isWindows()) {
|
|
f = new File("C:\\Windows\\Temp");
|
|
} else {
|
|
f = new File("/tmp");
|
|
}
|
|
|
|
logger.warn("Failed to get the temporary directory; falling back to: {}", f);
|
|
return f;
|
|
}
|
|
|
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
|
private static File toDirectory(String path) {
|
|
if (path == null) {
|
|
return null;
|
|
}
|
|
|
|
File f = new File(path);
|
|
f.mkdirs();
|
|
|
|
if (!f.isDirectory()) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return f.getAbsoluteFile();
|
|
} catch (Exception ignored) {
|
|
return f;
|
|
}
|
|
}
|
|
|
|
private static int bitMode0() {
|
|
// Check user-specified bit mode first.
|
|
int bitMode = SystemPropertyUtil.getInt("io.netty.bitMode", 0);
|
|
if (bitMode > 0) {
|
|
logger.debug("-Dio.netty.bitMode: {}", bitMode);
|
|
return bitMode;
|
|
}
|
|
|
|
// And then the vendor specific ones which is probably most reliable.
|
|
bitMode = SystemPropertyUtil.getInt("sun.arch.data.model", 0);
|
|
if (bitMode > 0) {
|
|
logger.debug("-Dio.netty.bitMode: {} (sun.arch.data.model)", bitMode);
|
|
return bitMode;
|
|
}
|
|
bitMode = SystemPropertyUtil.getInt("com.ibm.vm.bitmode", 0);
|
|
if (bitMode > 0) {
|
|
logger.debug("-Dio.netty.bitMode: {} (com.ibm.vm.bitmode)", bitMode);
|
|
return bitMode;
|
|
}
|
|
|
|
// os.arch also gives us a good hint.
|
|
String arch = SystemPropertyUtil.get("os.arch", "").toLowerCase(Locale.US).trim();
|
|
if ("amd64".equals(arch) || "x86_64".equals(arch)) {
|
|
bitMode = 64;
|
|
} else if ("i386".equals(arch) || "i486".equals(arch) || "i586".equals(arch) || "i686".equals(arch)) {
|
|
bitMode = 32;
|
|
}
|
|
|
|
if (bitMode > 0) {
|
|
logger.debug("-Dio.netty.bitMode: {} (os.arch: {})", bitMode, arch);
|
|
}
|
|
|
|
// Last resort: guess from VM name and then fall back to most common 64-bit mode.
|
|
String vm = SystemPropertyUtil.get("java.vm.name", "").toLowerCase(Locale.US);
|
|
Pattern bitPattern = Pattern.compile("([1-9][0-9]+)-?bit");
|
|
Matcher m = bitPattern.matcher(vm);
|
|
if (m.find()) {
|
|
return Integer.parseInt(m.group(1));
|
|
} else {
|
|
return 64;
|
|
}
|
|
}
|
|
|
|
private static int addressSize0() {
|
|
if (!hasUnsafe()) {
|
|
return -1;
|
|
}
|
|
return PlatformDependent0.addressSize();
|
|
}
|
|
|
|
private static long byteArrayBaseOffset0() {
|
|
if (!hasUnsafe()) {
|
|
return -1;
|
|
}
|
|
return PlatformDependent0.byteArrayBaseOffset();
|
|
}
|
|
|
|
private static boolean equalsSafe(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
|
|
final int end = startPos1 + length;
|
|
for (; startPos1 < end; ++startPos1, ++startPos2) {
|
|
if (bytes1[startPos1] != bytes2[startPos2]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static boolean isZeroSafe(byte[] bytes, int startPos, int length) {
|
|
final int end = startPos + length;
|
|
for (; startPos < end; ++startPos) {
|
|
if (bytes[startPos] != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Package private for testing purposes only!
|
|
*/
|
|
static int hashCodeAsciiSafe(byte[] bytes, int startPos, int length) {
|
|
int hash = HASH_CODE_ASCII_SEED;
|
|
final int remainingBytes = length & 7;
|
|
final int end = startPos + remainingBytes;
|
|
for (int i = startPos - 8 + length; i >= end; i -= 8) {
|
|
hash = PlatformDependent0.hashCodeAsciiCompute(getLongSafe(bytes, i), hash);
|
|
}
|
|
switch(remainingBytes) {
|
|
case 7:
|
|
return ((hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]))
|
|
* HASH_CODE_C2 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos + 1)))
|
|
* HASH_CODE_C1 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos + 3));
|
|
case 6:
|
|
return (hash * HASH_CODE_C1 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos)))
|
|
* HASH_CODE_C2 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos + 2));
|
|
case 5:
|
|
return (hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]))
|
|
* HASH_CODE_C2 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos + 1));
|
|
case 4:
|
|
return hash * HASH_CODE_C1 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos));
|
|
case 3:
|
|
return (hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]))
|
|
* HASH_CODE_C2 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos + 1));
|
|
case 2:
|
|
return hash * HASH_CODE_C1 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos));
|
|
case 1:
|
|
return hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]);
|
|
default:
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
public static String normalizedArch() {
|
|
return NORMALIZED_ARCH;
|
|
}
|
|
|
|
public static String normalizedOs() {
|
|
return NORMALIZED_OS;
|
|
}
|
|
|
|
public static Set<String> normalizedLinuxClassifiers() {
|
|
return LINUX_OS_CLASSIFIERS;
|
|
}
|
|
|
|
/**
|
|
* Adds only those classifier strings to <tt>dest</tt> which are present in <tt>allowed</tt>.
|
|
*
|
|
* @param allowed allowed classifiers
|
|
* @param dest destination set
|
|
* @param maybeClassifiers potential classifiers to add
|
|
*/
|
|
private static void addClassifier(Set<String> allowed, Set<String> dest, String... maybeClassifiers) {
|
|
for (String id : maybeClassifiers) {
|
|
if (allowed.contains(id)) {
|
|
dest.add(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static String normalizeOsReleaseVariableValue(String value) {
|
|
// Variable assignment values may be enclosed in double or single quotes.
|
|
return value.trim().replaceAll("[\"']", "");
|
|
}
|
|
|
|
private static String normalize(String value) {
|
|
return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
|
|
}
|
|
|
|
private static String normalizeArch(String value) {
|
|
value = normalize(value);
|
|
if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) {
|
|
return "x86_64";
|
|
}
|
|
if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) {
|
|
return "x86_32";
|
|
}
|
|
if (value.matches("^(ia64|itanium64)$")) {
|
|
return "itanium_64";
|
|
}
|
|
if (value.matches("^(sparc|sparc32)$")) {
|
|
return "sparc_32";
|
|
}
|
|
if (value.matches("^(sparcv9|sparc64)$")) {
|
|
return "sparc_64";
|
|
}
|
|
if (value.matches("^(arm|arm32)$")) {
|
|
return "arm_32";
|
|
}
|
|
if ("aarch64".equals(value)) {
|
|
return "aarch_64";
|
|
}
|
|
if (value.matches("^(ppc|ppc32)$")) {
|
|
return "ppc_32";
|
|
}
|
|
if ("ppc64".equals(value)) {
|
|
return "ppc_64";
|
|
}
|
|
if ("ppc64le".equals(value)) {
|
|
return "ppcle_64";
|
|
}
|
|
if ("s390".equals(value)) {
|
|
return "s390_32";
|
|
}
|
|
if ("s390x".equals(value)) {
|
|
return "s390_64";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
private static String normalizeOs(String value) {
|
|
value = normalize(value);
|
|
if (value.startsWith("aix")) {
|
|
return "aix";
|
|
}
|
|
if (value.startsWith("hpux")) {
|
|
return "hpux";
|
|
}
|
|
if (value.startsWith("os400")) {
|
|
// Avoid the names such as os4000
|
|
if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) {
|
|
return "os400";
|
|
}
|
|
}
|
|
if (value.startsWith("linux")) {
|
|
return "linux";
|
|
}
|
|
if (value.startsWith("macosx") || value.startsWith("osx")) {
|
|
return "osx";
|
|
}
|
|
if (value.startsWith("freebsd")) {
|
|
return "freebsd";
|
|
}
|
|
if (value.startsWith("openbsd")) {
|
|
return "openbsd";
|
|
}
|
|
if (value.startsWith("netbsd")) {
|
|
return "netbsd";
|
|
}
|
|
if (value.startsWith("solaris") || value.startsWith("sunos")) {
|
|
return "sunos";
|
|
}
|
|
if (value.startsWith("windows")) {
|
|
return "windows";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
private PlatformDependent() {
|
|
// only static method supported
|
|
}
|
|
}
|