diff --git a/.hgtags b/.hgtags
index 429399ed0ae..909c514bb0a 100644
--- a/.hgtags
+++ b/.hgtags
@@ -449,3 +449,4 @@ a85884d55ce32799f5c7382b7ea4839052b362a2 jdk-10+21
e5357aa85dadacc6562175ff74714fecfb4470cf jdk-10+22
22850b3a55240253841b9a425ad60a7fcdb22d47 jdk-10+23
3b201865d5c1f244f555cad58da599c9261286d8 jdk-10+24
+8eb5e3ccee560c28ac9b1df2670adac2b3d36fad jdk-10+25
diff --git a/bin/jib.sh b/bin/jib.sh
index 040e33c79c0..1adec9b0b45 100644
--- a/bin/jib.sh
+++ b/bin/jib.sh
@@ -28,8 +28,8 @@
mydir="$(dirname "${BASH_SOURCE[0]}")"
myname="$(basename "${BASH_SOURCE[0]}")"
-installed_jib_script=${mydir}/../../.jib/jib
-install_data=${mydir}/../../.jib/.data
+installed_jib_script=${mydir}/../.jib/jib
+install_data=${mydir}/../.jib/.data
setup_url() {
if [ -f ~/.config/jib/jib.conf ]; then
@@ -42,7 +42,7 @@ setup_url() {
jib_revision="2.0-SNAPSHOT"
jib_ext="jib.sh.gz"
- closed_script="${mydir}/../../../closed/conf/jib-install.conf"
+ closed_script="${mydir}/../../closed/make/conf/jib-install.conf"
if [ -f "${closed_script}" ]; then
source "${closed_script}"
fi
diff --git a/doc/nashorn/JavaScriptingProgrammersGuide.html b/doc/nashorn/JavaScriptingProgrammersGuide.html
index dd74d74903e..ae56c3fadf6 100644
--- a/doc/nashorn/JavaScriptingProgrammersGuide.html
+++ b/doc/nashorn/JavaScriptingProgrammersGuide.html
@@ -127,7 +127,7 @@ scripting language.
Scripting Package
-The Java Scripting functionality is in the javax.script
+The Java Scripting functionality is in the javax.script
package. This is a relatively small, simple API. The starting point
of the scripting API is the ScriptEngineManager
class.
A ScriptEngineManager object can discover script engines through
diff --git a/make/BuildNashorn.gmk b/make/BuildNashorn.gmk
index f176f5b94d2..1922dac2a41 100644
--- a/make/BuildNashorn.gmk
+++ b/make/BuildNashorn.gmk
@@ -41,7 +41,7 @@ JDK_CLASSES := $(call PathList, $(strip $(addprefix $(JDK_OUTPUTDIR)/modules/, \
$(eval $(call SetupJavaCompiler, GENERATE_NEWBYTECODE_DEBUG, \
JVM := $(JAVA_JAVAC), \
JAVAC := $(NEW_JAVAC), \
- FLAGS := -g -source 9 -target 9 --upgrade-module-path "$(JDK_OUTPUTDIR)/modules/" \
+ FLAGS := -g -source 10 -target 10 --upgrade-module-path "$(JDK_OUTPUTDIR)/modules/" \
--system none --module-source-path $(call GetModuleSrcPath), \
SERVER_DIR := $(SJAVAC_SERVER_DIR), \
SERVER_JVM := $(SJAVAC_SERVER_JAVA)))
diff --git a/make/InitSupport.gmk b/make/InitSupport.gmk
index 25c069885c3..7a7a7cad62c 100644
--- a/make/InitSupport.gmk
+++ b/make/InitSupport.gmk
@@ -36,7 +36,7 @@ ifeq ($(HAS_SPEC),)
# Include the corresponding closed file, if present.
# Normal hook mechanism cannot be used since we have no SPEC.
- -include $(topdir)/closed/make/InitSupport.gmk
+ -include $(topdir)/../closed/make/InitSupport.gmk
##############################################################################
# Helper functions for the initial part of Init.gmk, before the spec file is
diff --git a/make/autoconf/flags.m4 b/make/autoconf/flags.m4
index d58b43e9bf5..c69c8519925 100644
--- a/make/autoconf/flags.m4
+++ b/make/autoconf/flags.m4
@@ -1311,6 +1311,7 @@ AC_DEFUN([FLAGS_SETUP_COMPILER_FLAGS_FOR_JDK_HELPER],
$2LDFLAGS_JDKLIB="${$2LDFLAGS_JDK}"
$2LDFLAGS_JDKLIB="${$2LDFLAGS_JDKLIB} ${SHARED_LIBRARY_FLAGS}"
+ $2LDFLAGS_JDKLIB="${$2LDFLAGS_JDKLIB} ${LDFLAGS_NO_EXEC_STACK}"
if test "x$TOOLCHAIN_TYPE" = xmicrosoft; then
$2JAVA_BASE_LDFLAGS="${$2JAVA_BASE_LDFLAGS} \
-libpath:${OUTPUTDIR}/support/modules_libs/java.base"
@@ -1388,6 +1389,7 @@ $2LDFLAGS_JDKLIB="${$2LDFLAGS_JDKLIB} ${$2JAVA_BASE_LDFLAGS}"
AC_SUBST($2JDKEXE_LIBS)
AC_SUBST($2LDFLAGS_CXX_JDK)
AC_SUBST($2LDFLAGS_HASH_STYLE)
+ AC_SUBST($2LDFLAGS_NO_EXEC_STACK)
AC_SUBST($2JVM_CFLAGS)
AC_SUBST($2JVM_LDFLAGS)
diff --git a/make/autoconf/generated-configure.sh b/make/autoconf/generated-configure.sh
index defd16fad39..ebf810737ec 100644
--- a/make/autoconf/generated-configure.sh
+++ b/make/autoconf/generated-configure.sh
@@ -723,6 +723,7 @@ OPENJDK_BUILD_JVM_LIBS
OPENJDK_BUILD_JVM_ASFLAGS
OPENJDK_BUILD_JVM_LDFLAGS
OPENJDK_BUILD_JVM_CFLAGS
+OPENJDK_BUILD_LDFLAGS_NO_EXEC_STACK
OPENJDK_BUILD_LDFLAGS_HASH_STYLE
OPENJDK_BUILD_LDFLAGS_CXX_JDK
OPENJDK_BUILD_JDKEXE_LIBS
@@ -738,6 +739,7 @@ JVM_LIBS
JVM_ASFLAGS
JVM_LDFLAGS
JVM_CFLAGS
+LDFLAGS_NO_EXEC_STACK
LDFLAGS_HASH_STYLE
LDFLAGS_CXX_JDK
JDKEXE_LIBS
@@ -5115,7 +5117,7 @@ VS_SDK_PLATFORM_NAME_2013=
#CUSTOM_AUTOCONF_INCLUDE
# Do not change or remove the following line, it is needed for consistency checks:
-DATE_WHEN_GENERATED=1506333008
+DATE_WHEN_GENERATED=1506397140
###############################################################################
#
@@ -52024,6 +52026,7 @@ fi
LDFLAGS_JDKLIB="${LDFLAGS_JDK}"
LDFLAGS_JDKLIB="${LDFLAGS_JDKLIB} ${SHARED_LIBRARY_FLAGS}"
+ LDFLAGS_JDKLIB="${LDFLAGS_JDKLIB} ${LDFLAGS_NO_EXEC_STACK}"
if test "x$TOOLCHAIN_TYPE" = xmicrosoft; then
JAVA_BASE_LDFLAGS="${JAVA_BASE_LDFLAGS} \
-libpath:${OUTPUTDIR}/support/modules_libs/java.base"
@@ -52109,6 +52112,7 @@ LDFLAGS_JDKLIB="${LDFLAGS_JDKLIB} ${JAVA_BASE_LDFLAGS}"
+
# Special extras...
if test "x$TOOLCHAIN_TYPE" = xsolstudio; then
if test "x$OPENJDK_BUILD_CPU_ARCH" = "xsparc"; then
@@ -52903,6 +52907,7 @@ fi
OPENJDK_BUILD_LDFLAGS_JDKLIB="${OPENJDK_BUILD_LDFLAGS_JDK}"
OPENJDK_BUILD_LDFLAGS_JDKLIB="${OPENJDK_BUILD_LDFLAGS_JDKLIB} ${SHARED_LIBRARY_FLAGS}"
+ OPENJDK_BUILD_LDFLAGS_JDKLIB="${OPENJDK_BUILD_LDFLAGS_JDKLIB} ${LDFLAGS_NO_EXEC_STACK}"
if test "x$TOOLCHAIN_TYPE" = xmicrosoft; then
OPENJDK_BUILD_JAVA_BASE_LDFLAGS="${OPENJDK_BUILD_JAVA_BASE_LDFLAGS} \
-libpath:${OUTPUTDIR}/support/modules_libs/java.base"
@@ -52988,6 +52993,7 @@ OPENJDK_BUILD_LDFLAGS_JDKLIB="${OPENJDK_BUILD_LDFLAGS_JDKLIB} ${OPENJDK_BUILD_JA
+
# Tests are only ever compiled for TARGET
# Flags for compiling test libraries
CFLAGS_TESTLIB="$COMMON_CCXXFLAGS_JDK $CFLAGS_JDK $PICFLAG $CFLAGS_JDKLIB_EXTRA"
diff --git a/make/autoconf/spec.gmk.in b/make/autoconf/spec.gmk.in
index 9cc8145709e..84babe101d4 100644
--- a/make/autoconf/spec.gmk.in
+++ b/make/autoconf/spec.gmk.in
@@ -387,6 +387,7 @@ CFLAGS_JDKEXE:=@CFLAGS_JDKEXE@
CXXFLAGS_JDKEXE:=@CXXFLAGS_JDKEXE@
LDFLAGS_HASH_STYLE := @LDFLAGS_HASH_STYLE@
+LDFLAGS_NO_EXEC_STACK := @LDFLAGS_NO_EXEC_STACK@
JVM_CFLAGS := @JVM_CFLAGS@
JVM_CFLAGS_SYMBOLS := @JVM_CFLAGS_SYMBOLS@
diff --git a/make/common/Modules.gmk b/make/common/Modules.gmk
index 736e0607725..471b1a68064 100644
--- a/make/common/Modules.gmk
+++ b/make/common/Modules.gmk
@@ -58,7 +58,6 @@ BOOT_MODULES += \
java.rmi \
java.security.sasl \
java.xml \
- jdk.httpserver \
jdk.internal.vm.ci \
jdk.management \
jdk.management.agent \
@@ -112,6 +111,7 @@ PLATFORM_MODULES += \
jdk.crypto.cryptoki \
jdk.crypto.ec \
jdk.dynalink \
+ jdk.httpserver \
jdk.incubator.httpclient \
jdk.internal.vm.compiler.management \
jdk.jsobject \
diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js
index b2dfdf391ed..2758d9772e1 100644
--- a/make/conf/jib-profiles.js
+++ b/make/conf/jib-profiles.js
@@ -900,6 +900,45 @@ var getJibProfilesProfiles = function (input, common, data) {
}
},
+ "windows-x64-open": {
+ artifacts: {
+ jdk: {
+ local: "bundles/\\(jdk.*bin.tar.gz\\)",
+ remote: [
+ "bundles/openjdk/GPL/windows-x64/jdk-" + data.version
+ + "_windows-x64_bin.tar.gz",
+ "bundles/openjdk/GPL/windows-x64/\\1"
+ ],
+ subdir: "jdk-" + data.version
+ },
+ jre: {
+ local: "bundles/\\(jre.*bin.tar.gz\\)",
+ remote: "bundles/openjdk/GPL/windows-x64/\\1"
+ },
+ test: {
+ local: "bundles/\\(jdk.*bin-tests.tar.gz\\)",
+ remote: [
+ "bundles/openjdk/GPL/windows-x64/jdk-" + data.version
+ + "_windows-x64_bin-tests.tar.gz",
+ "bundles/openjdk/GPL/windows-x64/\\1"
+ ]
+ },
+ jdk_symbols: {
+ local: "bundles/\\(jdk.*bin-symbols.tar.gz\\)",
+ remote: [
+ "bundles/openjdk/GPL/windows-x64/jdk-" + data.version
+ + "_windows-x64_bin-symbols.tar.gz",
+ "bundles/openjdk/GPL/windows-x64/\\1"
+ ],
+ subdir: "jdk-" + data.version
+ },
+ jre_symbols: {
+ local: "bundles/\\(jre.*bin-symbols.tar.gz\\)",
+ remote: "bundles/openjdk/GPL/windows-x64/\\1",
+ }
+ }
+ },
+
"linux-x86-open-debug": {
artifacts: {
jdk: {
@@ -929,9 +968,10 @@ var getJibProfilesProfiles = function (input, common, data) {
profiles["linux-x86-ri-debug"] = clone(profiles["linux-x86-open-debug"]);
profiles["macosx-x64-ri"] = clone(profiles["macosx-x64-open"]);
profiles["windows-x86-ri"] = clone(profiles["windows-x86-open"]);
+ profiles["windows-x64-ri"] = clone(profiles["windows-x64-open"]);
// Generate artifacts for ri profiles
- [ "linux-x64-ri", "linux-x86-ri", "linux-x86-ri-debug", "macosx-x64-ri", "windows-x86-ri" ]
+ [ "linux-x64-ri", "linux-x86-ri", "linux-x86-ri-debug", "macosx-x64-ri", "windows-x86-ri", "windows-x64-ri" ]
.forEach(function (name) {
// Rewrite all remote dirs to "bundles/openjdk/BCL/..."
for (artifactName in profiles[name].artifacts) {
@@ -947,6 +987,11 @@ var getJibProfilesProfiles = function (input, common, data) {
configure_args: "--with-freetype-license="
+ input.get("freetype", "install_path")
+ "/freetype-2.7.1-v120-x86/freetype.md"
+ },
+ "windows-x64-ri": {
+ configure_args: "--with-freetype-license="
+ + input.get("freetype", "install_path")
+ + "/freetype-2.7.1-v120-x64/freetype.md"
}
};
profiles = concatObjects(profiles, profilesRiFreetype);
diff --git a/make/hotspot/lib/CompileLibjsig.gmk b/make/hotspot/lib/CompileLibjsig.gmk
index fbe557cd420..9c634296e96 100644
--- a/make/hotspot/lib/CompileLibjsig.gmk
+++ b/make/hotspot/lib/CompileLibjsig.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@@ -36,7 +36,7 @@ ifneq ($(OPENJDK_TARGET_OS), windows)
ifeq ($(STATIC_BUILD), false)
ifeq ($(OPENJDK_TARGET_OS), linux)
LIBJSIG_CFLAGS := -fPIC -D_GNU_SOURCE -D_REENTRANT $(EXTRA_CFLAGS)
- LIBJSIG_LDFLAGS := $(LDFLAGS_HASH_STYLE) $(EXTRA_CFLAGS)
+ LIBJSIG_LDFLAGS := $(LDFLAGS_HASH_STYLE) ${LDFLAGS_NO_EXEC_STACK} $(EXTRA_CFLAGS)
LIBJSIG_LIBS := $(LIBDL)
# NOTE: The old build compiled this library without -soname.
diff --git a/make/langtools/src/classes/build/tools/symbolgenerator/TransitiveDependencies.java b/make/langtools/src/classes/build/tools/symbolgenerator/TransitiveDependencies.java
index 4fa98b83edc..d64899d8705 100644
--- a/make/langtools/src/classes/build/tools/symbolgenerator/TransitiveDependencies.java
+++ b/make/langtools/src/classes/build/tools/symbolgenerator/TransitiveDependencies.java
@@ -57,8 +57,8 @@ public class TransitiveDependencies {
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- List options = Arrays.asList("-source", "9",
- "-target", "9",
+ List options = Arrays.asList("-source", "10",
+ "-target", "10",
"-proc:only",
"--system", "none",
"--module-source-path", args[0],
diff --git a/make/nashorn/build.xml b/make/nashorn/build.xml
index 49fffbc7d42..39b01d6b699 100644
--- a/make/nashorn/build.xml
+++ b/make/nashorn/build.xml
@@ -174,8 +174,6 @@
@@ -190,8 +188,6 @@
@@ -207,8 +203,6 @@
@@ -342,8 +336,6 @@
@@ -351,7 +343,7 @@
-
+
diff --git a/make/nashorn/buildtools/nasgen/project.properties b/make/nashorn/buildtools/nasgen/project.properties
index ec31db01d12..fcec900fd2d 100644
--- a/make/nashorn/buildtools/nasgen/project.properties
+++ b/make/nashorn/buildtools/nasgen/project.properties
@@ -24,8 +24,6 @@ application.title=nasgen
# source and target levels
build.compiler=modern
-javac.source=1.7
-javac.target=1.7
# This directory is removed when the project is cleaned:
nasgen.build.dir=../../../../build/nashorn/nasgen
diff --git a/make/nashorn/buildtools/nashorntask/project.properties b/make/nashorn/buildtools/nashorntask/project.properties
index 3032823acb2..19e0b198aee 100644
--- a/make/nashorn/buildtools/nashorntask/project.properties
+++ b/make/nashorn/buildtools/nashorntask/project.properties
@@ -24,8 +24,6 @@ application.title=nashorntask
# source and target levels
build.compiler=modern
-javac.source=1.8
-javac.target=1.8
# This directory is removed when the project is cleaned:
nashorntask.build.dir=../../../../build/nashorn/nashorntask
diff --git a/make/nashorn/project.properties b/make/nashorn/project.properties
index d8f4c9ebcb2..54752bd7a75 100644
--- a/make/nashorn/project.properties
+++ b/make/nashorn/project.properties
@@ -32,8 +32,6 @@ jdk.jline.src.dir=src/jdk.internal.le/share/classes
# source and target levels
build.compiler=modern
-javac.source=1.9
-javac.target=1.9
javadoc.option=\
-tag "implSpec:a:Implementation Requirements:" \
@@ -146,7 +144,7 @@ javac.test.classpath=\
${file.reference.bsh.jar}${path.separator}\
${file.reference.snakeyaml.jar}
-test.module.imports=\
+test.module.imports.compile.time=\
--add-exports jdk.scripting.nashorn/jdk.nashorn.internal.ir=ALL-UNNAMED \
--add-exports jdk.scripting.nashorn/jdk.nashorn.internal.codegen=ALL-UNNAMED \
--add-exports jdk.scripting.nashorn/jdk.nashorn.internal.parser=ALL-UNNAMED \
@@ -159,7 +157,10 @@ test.module.imports=\
--add-exports jdk.scripting.nashorn/jdk.nashorn.internal.runtime.regexp=ALL-UNNAMED \
--add-exports jdk.scripting.nashorn/jdk.nashorn.internal.runtime.regexp.joni=ALL-UNNAMED \
--add-exports jdk.scripting.nashorn/jdk.nashorn.tools=ALL-UNNAMED \
- --add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED \
+ --add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED
+
+test.module.imports.runtime=\
+ ${test.module.imports.compile.time} \
--add-opens jdk.scripting.nashorn/jdk.nashorn.internal.runtime=ALL-UNNAMED \
--add-opens jdk.scripting.nashorn/jdk.nashorn.internal.runtime.doubleconv=ALL-UNNAMED
@@ -359,7 +360,7 @@ run.test.user.country=TR
run.test.jvmargs.common=\
-server \
- ${test.module.imports} \
+ ${test.module.imports.runtime} \
${run.test.jvmargs.external} \
--add-modules jdk.scripting.nashorn.shell \
${nashorn.override.option} \
diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp
index dab43205470..7d1567185e4 100644
--- a/src/hotspot/share/prims/whitebox.cpp
+++ b/src/hotspot/share/prims/whitebox.cpp
@@ -73,6 +73,9 @@
#include "utilities/nativeCallStack.hpp"
#endif // INCLUDE_NMT
+#ifdef LINUX
+#include "utilities/elfFile.hpp"
+#endif
#define SIZE_T_MAX_VALUE ((size_t) -1)
@@ -1823,6 +1826,20 @@ WB_ENTRY(void, WB_RemoveCompilerDirective(JNIEnv* env, jobject o, jint count))
DirectivesStack::pop(count);
WB_END
+// Checks that the library libfile has the noexecstack bit set.
+WB_ENTRY(jboolean, WB_CheckLibSpecifiesNoexecstack(JNIEnv* env, jobject o, jstring libfile))
+ jboolean ret = false;
+#ifdef LINUX
+ // Can't be in VM when we call JNI.
+ ThreadToNativeFromVM ttnfv(thread);
+ const char* lf = env->GetStringUTFChars(libfile, NULL);
+ CHECK_JNI_EXCEPTION_(env, 0);
+ ret = (jboolean) ElfFile::specifies_noexecstack(lf);
+ env->ReleaseStringUTFChars(libfile, lf);
+#endif
+ return ret;
+WB_END
+
#define CC (char*)
static JNINativeMethod methods[] = {
@@ -2027,6 +2044,8 @@ static JNINativeMethod methods[] = {
(void*)&WB_GetConcurrentGCPhases},
{CC"requestConcurrentGCPhase0", CC"(Ljava/lang/String;)Z",
(void*)&WB_RequestConcurrentGCPhase},
+ {CC"checkLibSpecifiesNoexecstack", CC"(Ljava/lang/String;)Z",
+ (void*)&WB_CheckLibSpecifiesNoexecstack},
};
#undef CC
diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java
index 4fb61d1e586..7815bde0c61 100644
--- a/src/java.base/share/classes/java/lang/ClassLoader.java
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java
@@ -2160,10 +2160,12 @@ public abstract class ClassLoader {
* if a package of the given {@code name} is already
* defined by this class loader
*
+ *
* @since 1.2
* @revised 9
* @spec JPMS
*
+ * @jvms 5.3 Run-time package
* @see
* The JAR File Specification: Package Sealing
*/
@@ -2186,17 +2188,19 @@ public abstract class ClassLoader {
}
/**
- * Returns a {@code Package} of the given name that has been
- * defined by this class loader.
+ * Returns a {@code Package} of the given name that
+ * has been defined by this class loader.
*
* @param name The package name
*
- * @return The {@code Package} of the given name defined by this class loader,
- * or {@code null} if not found
+ * @return The {@code Package} of the given name that has been defined
+ * by this class loader, or {@code null} if not found
*
* @throws NullPointerException
* if {@code name} is {@code null}.
*
+ * @jvms 5.3 Run-time package
+ *
* @since 9
* @spec JPMS
*/
@@ -2211,14 +2215,18 @@ public abstract class ClassLoader {
}
/**
- * Returns all of the {@code Package}s defined by this class loader.
- * The returned array has no duplicated {@code Package}s of the same name.
+ * Returns all of the {@code Package}s that have been defined by
+ * this class loader. The returned array has no duplicated {@code Package}s
+ * of the same name.
*
* @apiNote This method returns an array rather than a {@code Set} or {@code Stream}
* for consistency with the existing {@link #getPackages} method.
*
- * @return The array of {@code Package} objects defined by this class loader;
- * or an zero length array if no package has been defined by this class loader.
+ * @return The array of {@code Package} objects that have been defined by
+ * this class loader; or an zero length array if no package has been
+ * defined by this class loader.
+ *
+ * @jvms 5.3 Run-time package
*
* @since 9
* @spec JPMS
@@ -2244,7 +2252,7 @@ public abstract class ClassLoader {
* @param name
* The package name
*
- * @return The {@code Package} corresponding to the given name defined by
+ * @return The {@code Package} of the given name that has been defined by
* this class loader or its ancestors, or {@code null} if not found.
*
* @throws NullPointerException
@@ -2263,6 +2271,8 @@ public abstract class ClassLoader {
* {@link ClassLoader#getDefinedPackage} method which returns
* a {@code Package} for the specified class loader.
*
+ * @see ClassLoader#getDefinedPackage(String)
+ *
* @since 1.2
* @revised 9
* @spec JPMS
@@ -2281,10 +2291,10 @@ public abstract class ClassLoader {
}
/**
- * Returns all of the {@code Package}s defined by this class loader
- * and its ancestors. The returned array may contain more than one
- * {@code Package} object of the same package name, each defined by
- * a different class loader in the class loader hierarchy.
+ * Returns all of the {@code Package}s that have been defined by
+ * this class loader and its ancestors. The returned array may contain
+ * more than one {@code Package} object of the same package name, each
+ * defined by a different class loader in the class loader hierarchy.
*
* @apiNote The {@link #getPlatformClassLoader() platform class loader}
* may delegate to the application class loader. In other words,
@@ -2294,8 +2304,10 @@ public abstract class ClassLoader {
* when invoked on the platform class loader, this method will not
* return any packages defined to the application class loader.
*
- * @return The array of {@code Package} objects defined by this
- * class loader and its ancestors
+ * @return The array of {@code Package} objects that have been defined by
+ * this class loader and its ancestors
+ *
+ * @see ClassLoader#getDefinedPackages()
*
* @since 1.2
* @revised 9
diff --git a/src/java.base/share/classes/java/lang/StackFrameInfo.java b/src/java.base/share/classes/java/lang/StackFrameInfo.java
index 4f3cc1e980c..3666a834218 100644
--- a/src/java.base/share/classes/java/lang/StackFrameInfo.java
+++ b/src/java.base/share/classes/java/lang/StackFrameInfo.java
@@ -29,6 +29,7 @@ import jdk.internal.misc.SharedSecrets;
import static java.lang.StackWalker.Option.*;
import java.lang.StackWalker.StackFrame;
+import java.lang.invoke.MethodType;
class StackFrameInfo implements StackFrame {
private final static JavaLangInvokeAccess JLIA =
@@ -78,6 +79,17 @@ class StackFrameInfo implements StackFrame {
return JLIA.getName(memberName);
}
+ @Override
+ public MethodType getMethodType() {
+ walker.ensureAccessEnabled(RETAIN_CLASS_REFERENCE);
+ return JLIA.getMethodType(memberName);
+ }
+
+ @Override
+ public String getDescriptor() {
+ return JLIA.getMethodDescriptor(memberName);
+ }
+
@Override
public int getByteCodeIndex() {
// bci not available for native methods
diff --git a/src/java.base/share/classes/java/lang/StackWalker.java b/src/java.base/share/classes/java/lang/StackWalker.java
index 7e4bc1ebf88..a079ac53384 100644
--- a/src/java.base/share/classes/java/lang/StackWalker.java
+++ b/src/java.base/share/classes/java/lang/StackWalker.java
@@ -26,10 +26,12 @@ package java.lang;
import jdk.internal.reflect.CallerSensitive;
-import java.util.*;
+import java.lang.invoke.MethodType;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.stream.Stream;
/**
@@ -96,7 +98,7 @@ public final class StackWalker {
* @since 9
* @jvms 2.6
*/
- public static interface StackFrame {
+ public interface StackFrame {
/**
* Gets the binary name
* of the declaring class of the method represented by this stack frame.
@@ -127,6 +129,47 @@ public final class StackWalker {
*/
public Class> getDeclaringClass();
+ /**
+ * Returns the {@link MethodType} representing the parameter types and
+ * the return type for the method represented by this stack frame.
+ *
+ * @implSpec
+ * The default implementation throws {@code UnsupportedOperationException}.
+ *
+ * @return the {@code MethodType} for this stack frame
+ *
+ * @throws UnsupportedOperationException if this {@code StackWalker}
+ * is not configured with {@link Option#RETAIN_CLASS_REFERENCE
+ * Option.RETAIN_CLASS_REFERENCE}.
+ *
+ * @since 10
+ */
+ public default MethodType getMethodType() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the descriptor of the method represented by
+ * this stack frame as defined by
+ * The Java Virtual Machine Specification.
+ *
+ * @implSpec
+ * The default implementation throws {@code UnsupportedOperationException}.
+ *
+ * @return the descriptor of the method represented by
+ * this stack frame
+ *
+ * @see MethodType#fromMethodDescriptorString(String, ClassLoader)
+ * @see MethodType#toMethodDescriptorString()
+ * @jvms 4.3.3 Method Descriptor
+ *
+ * @since 10
+ */
+ public default String getDescriptor() {
+ throw new UnsupportedOperationException();
+ }
+
+
/**
* Returns the index to the code array of the {@code Code} attribute
* containing the execution point represented by this stack frame.
diff --git a/src/java.base/share/classes/java/lang/invoke/DelegatingMethodHandle.java b/src/java.base/share/classes/java/lang/invoke/DelegatingMethodHandle.java
index cecfe5efc34..2bcc33a3802 100644
--- a/src/java.base/share/classes/java/lang/invoke/DelegatingMethodHandle.java
+++ b/src/java.base/share/classes/java/lang/invoke/DelegatingMethodHandle.java
@@ -28,6 +28,7 @@ package java.lang.invoke;
import java.util.Arrays;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.LambdaForm.Kind.*;
+import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeVirtual;
import static java.lang.invoke.MethodHandleStatics.*;
/**
@@ -158,8 +159,11 @@ abstract class DelegatingMethodHandle extends MethodHandle {
static final NamedFunction NF_getTarget;
static {
try {
- NF_getTarget = new NamedFunction(DelegatingMethodHandle.class
- .getDeclaredMethod("getTarget"));
+ MemberName member = new MemberName(DelegatingMethodHandle.class, "getTarget",
+ MethodType.methodType(MethodHandle.class), REF_invokeVirtual);
+ NF_getTarget = new NamedFunction(
+ MemberName.getFactory()
+ .resolveOrFail(REF_invokeVirtual, member, DelegatingMethodHandle.class, NoSuchMethodException.class));
} catch (ReflectiveOperationException ex) {
throw newInternalError(ex);
}
diff --git a/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java b/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java
index bd06ffa5348..3c83d56d259 100644
--- a/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java
+++ b/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java
@@ -753,42 +753,38 @@ class DirectMethodHandle extends MethodHandle {
return nf;
}
+ private static final MethodType OBJ_OBJ_TYPE = MethodType.methodType(Object.class, Object.class);
+
+ private static final MethodType LONG_OBJ_TYPE = MethodType.methodType(long.class, Object.class);
+
private static NamedFunction createFunction(byte func) {
try {
switch (func) {
case NF_internalMemberName:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("internalMemberName", Object.class));
+ return getNamedFunction("internalMemberName", OBJ_OBJ_TYPE);
case NF_internalMemberNameEnsureInit:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("internalMemberNameEnsureInit", Object.class));
+ return getNamedFunction("internalMemberNameEnsureInit", OBJ_OBJ_TYPE);
case NF_ensureInitialized:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("ensureInitialized", Object.class));
+ return getNamedFunction("ensureInitialized", MethodType.methodType(void.class, Object.class));
case NF_fieldOffset:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("fieldOffset", Object.class));
+ return getNamedFunction("fieldOffset", LONG_OBJ_TYPE);
case NF_checkBase:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("checkBase", Object.class));
+ return getNamedFunction("checkBase", OBJ_OBJ_TYPE);
case NF_staticBase:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("staticBase", Object.class));
+ return getNamedFunction("staticBase", OBJ_OBJ_TYPE);
case NF_staticOffset:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("staticOffset", Object.class));
+ return getNamedFunction("staticOffset", LONG_OBJ_TYPE);
case NF_checkCast:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("checkCast", Object.class, Object.class));
+ return getNamedFunction("checkCast", MethodType.methodType(Object.class, Object.class, Object.class));
case NF_allocateInstance:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("allocateInstance", Object.class));
+ return getNamedFunction("allocateInstance", OBJ_OBJ_TYPE);
case NF_constructorMethod:
- return new NamedFunction(DirectMethodHandle.class
- .getDeclaredMethod("constructorMethod", Object.class));
+ return getNamedFunction("constructorMethod", OBJ_OBJ_TYPE);
case NF_UNSAFE:
- return new NamedFunction(new MemberName(MethodHandleStatics.class
- .getDeclaredField("UNSAFE")));
+ MemberName member = new MemberName(MethodHandleStatics.class, "UNSAFE", Unsafe.class, REF_getField);
+ return new NamedFunction(
+ MemberName.getFactory()
+ .resolveOrFail(REF_getField, member, DirectMethodHandle.class, NoSuchMethodException.class));
default:
throw newInternalError("Unknown function: " + func);
}
@@ -797,6 +793,15 @@ class DirectMethodHandle extends MethodHandle {
}
}
+ private static NamedFunction getNamedFunction(String name, MethodType type)
+ throws ReflectiveOperationException
+ {
+ MemberName member = new MemberName(DirectMethodHandle.class, name, type, REF_invokeStatic);
+ return new NamedFunction(
+ MemberName.getFactory()
+ .resolveOrFail(REF_invokeStatic, member, DirectMethodHandle.class, NoSuchMethodException.class));
+ }
+
static {
// The Holder class will contain pre-generated DirectMethodHandles resolved
// speculatively using MemberName.getFactory().resolveOrNull. However, that
diff --git a/src/java.base/share/classes/java/lang/invoke/Invokers.java b/src/java.base/share/classes/java/lang/invoke/Invokers.java
index 8eeb4c3954e..ba8cd72ef84 100644
--- a/src/java.base/share/classes/java/lang/invoke/Invokers.java
+++ b/src/java.base/share/classes/java/lang/invoke/Invokers.java
@@ -611,23 +611,17 @@ class Invokers {
try {
switch (func) {
case NF_checkExactType:
- return new NamedFunction(Invokers.class
- .getDeclaredMethod("checkExactType", MethodHandle.class, MethodType.class));
+ return getNamedFunction("checkExactType", MethodType.methodType(void.class, MethodHandle.class, MethodType.class));
case NF_checkGenericType:
- return new NamedFunction(Invokers.class
- .getDeclaredMethod("checkGenericType", MethodHandle.class, MethodType.class));
+ return getNamedFunction("checkGenericType", MethodType.methodType(MethodHandle.class, MethodHandle.class, MethodType.class));
case NF_getCallSiteTarget:
- return new NamedFunction(Invokers.class
- .getDeclaredMethod("getCallSiteTarget", CallSite.class));
+ return getNamedFunction("getCallSiteTarget", MethodType.methodType(MethodHandle.class, CallSite.class));
case NF_checkCustomized:
- return new NamedFunction(Invokers.class
- .getDeclaredMethod("checkCustomized", MethodHandle.class));
+ return getNamedFunction("checkCustomized", MethodType.methodType(void.class, MethodHandle.class));
case NF_checkVarHandleGenericType:
- return new NamedFunction(Invokers.class
- .getDeclaredMethod("checkVarHandleGenericType", VarHandle.class, VarHandle.AccessDescriptor.class));
+ return getNamedFunction("checkVarHandleGenericType", MethodType.methodType(MethodHandle.class, VarHandle.class, VarHandle.AccessDescriptor.class));
case NF_checkVarHandleExactType:
- return new NamedFunction(Invokers.class
- .getDeclaredMethod("checkVarHandleExactType", VarHandle.class, VarHandle.AccessDescriptor.class));
+ return getNamedFunction("checkVarHandleExactType", MethodType.methodType(MethodHandle.class, VarHandle.class, VarHandle.AccessDescriptor.class));
default:
throw newInternalError("Unknown function: " + func);
}
@@ -636,6 +630,15 @@ class Invokers {
}
}
+ private static NamedFunction getNamedFunction(String name, MethodType type)
+ throws ReflectiveOperationException
+ {
+ MemberName member = new MemberName(Invokers.class, name, type, REF_invokeStatic);
+ return new NamedFunction(
+ MemberName.getFactory()
+ .resolveOrFail(REF_invokeStatic, member, Invokers.class, NoSuchMethodException.class));
+ }
+
private static class Lazy {
private static final MethodHandle MH_asSpreader;
diff --git a/src/java.base/share/classes/java/lang/invoke/MemberName.java b/src/java.base/share/classes/java/lang/invoke/MemberName.java
index 86a625b2eed..c67f5fb3d89 100644
--- a/src/java.base/share/classes/java/lang/invoke/MemberName.java
+++ b/src/java.base/share/classes/java/lang/invoke/MemberName.java
@@ -162,6 +162,29 @@ import static java.lang.invoke.MethodHandleStatics.newInternalError;
return (MethodType) type;
}
+ /** Return the descriptor of this member, which
+ * must be a method or constructor.
+ */
+ String getMethodDescriptor() {
+ if (type == null) {
+ expandFromVM();
+ if (type == null) {
+ return null;
+ }
+ }
+ if (!isInvocable()) {
+ throw newIllegalArgumentException("not invocable, no method type");
+ }
+
+ // Get a snapshot of type which doesn't get changed by racing threads.
+ final Object type = this.type;
+ if (type instanceof String) {
+ return (String) type;
+ } else {
+ return getMethodType().toMethodDescriptorString();
+ }
+ }
+
/** Return the actual type under which this method or constructor must be invoked.
* For non-static methods or constructors, this is the type with a leading parameter,
* a reference to declaring class. For static methods, it is the same as the declared type.
diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
index 963e0cd24a8..348a15921f0 100644
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
@@ -1785,6 +1785,18 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
return memberName.getName();
}
+ @Override
+ public MethodType getMethodType(Object mname) {
+ MemberName memberName = (MemberName)mname;
+ return memberName.getMethodType();
+ }
+
+ @Override
+ public String getMethodDescriptor(Object mname) {
+ MemberName memberName = (MemberName)mname;
+ return memberName.getMethodDescriptor();
+ }
+
@Override
public boolean isNative(Object mname) {
MemberName memberName = (MemberName)mname;
diff --git a/src/java.base/share/classes/java/util/ArrayDeque.java b/src/java.base/share/classes/java/util/ArrayDeque.java
index 6cf4321aa77..f900f10fb25 100644
--- a/src/java.base/share/classes/java/util/ArrayDeque.java
+++ b/src/java.base/share/classes/java/util/ArrayDeque.java
@@ -211,7 +211,7 @@ public class ArrayDeque extends AbstractCollection
}
/**
- * Increments i, mod modulus.
+ * Circularly increments i, mod modulus.
* Precondition and postcondition: 0 <= i < modulus.
*/
static final int inc(int i, int modulus) {
@@ -220,7 +220,7 @@ public class ArrayDeque extends AbstractCollection
}
/**
- * Decrements i, mod modulus.
+ * Circularly decrements i, mod modulus.
* Precondition and postcondition: 0 <= i < modulus.
*/
static final int dec(int i, int modulus) {
@@ -233,7 +233,7 @@ public class ArrayDeque extends AbstractCollection
* Precondition: 0 <= i < modulus, 0 <= distance <= modulus.
* @return index 0 <= i < modulus
*/
- static final int add(int i, int distance, int modulus) {
+ static final int inc(int i, int distance, int modulus) {
if ((i += distance) - modulus >= 0) i -= modulus;
return i;
}
@@ -825,7 +825,7 @@ public class ArrayDeque extends AbstractCollection
final int i, n;
return ((n = sub(getFence(), i = cursor, es.length) >> 1) <= 0)
? null
- : new DeqSpliterator(i, cursor = add(i, n, es.length));
+ : new DeqSpliterator(i, cursor = inc(i, n, es.length));
}
public void forEachRemaining(Consumer super E> action) {
diff --git a/src/java.base/share/classes/java/util/HashMap.java b/src/java.base/share/classes/java/util/HashMap.java
index 4f0e8e6c7d6..4534656cd97 100644
--- a/src/java.base/share/classes/java/util/HashMap.java
+++ b/src/java.base/share/classes/java/util/HashMap.java
@@ -490,7 +490,7 @@ public class HashMap extends AbstractMap
}
/**
- * Implements Map.putAll and Map constructor
+ * Implements Map.putAll and Map constructor.
*
* @param m the map
* @param evict false when initially constructing this map, else
@@ -557,7 +557,7 @@ public class HashMap extends AbstractMap
}
/**
- * Implements Map.get and related methods
+ * Implements Map.get and related methods.
*
* @param hash hash for key
* @param key the key
@@ -612,7 +612,7 @@ public class HashMap extends AbstractMap
}
/**
- * Implements Map.put and related methods
+ * Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
@@ -700,7 +700,7 @@ public class HashMap extends AbstractMap
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
- Node[] newTab = (Node[])new Node[newCap];
+ Node[] newTab = (Node[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
@@ -800,7 +800,7 @@ public class HashMap extends AbstractMap
}
/**
- * Implements Map.remove and related methods
+ * Implements Map.remove and related methods.
*
* @param hash hash for key
* @param key the key
@@ -875,7 +875,7 @@ public class HashMap extends AbstractMap
public boolean containsValue(Object value) {
Node[] tab; V v;
if ((tab = table) != null && size > 0) {
- for (Node e : tab) {
+ for (Node e : tab) {
for (; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
@@ -927,7 +927,7 @@ public class HashMap extends AbstractMap
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
- for (Node e : tab) {
+ for (Node e : tab) {
for (; e != null; e = e.next)
action.accept(e.key);
}
@@ -975,7 +975,7 @@ public class HashMap extends AbstractMap
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
- for (Node e : tab) {
+ for (Node e : tab) {
for (; e != null; e = e.next)
action.accept(e.value);
}
@@ -1038,7 +1038,7 @@ public class HashMap extends AbstractMap
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
- for (Node e : tab) {
+ for (Node e : tab) {
for (; e != null; e = e.next)
action.accept(e);
}
@@ -1335,7 +1335,7 @@ public class HashMap extends AbstractMap
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
- for (Node e : tab) {
+ for (Node e : tab) {
for (; e != null; e = e.next)
action.accept(e.key, e.value);
}
@@ -1351,7 +1351,7 @@ public class HashMap extends AbstractMap
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
- for (Node e : tab) {
+ for (Node e : tab) {
for (; e != null; e = e.next) {
e.value = function.apply(e.key, e.value);
}
@@ -1394,9 +1394,10 @@ public class HashMap extends AbstractMap
}
/**
- * Save the state of the {@code HashMap} instance to a stream (i.e.,
- * serialize it).
+ * Saves this map to a stream (that is, serializes it).
*
+ * @param s the stream
+ * @throws IOException if an I/O error occurs
* @serialData The capacity of the HashMap (the length of the
* bucket array) is emitted (int), followed by the
* size (an int, the number of key-value
@@ -1415,8 +1416,11 @@ public class HashMap extends AbstractMap
}
/**
- * Reconstitute the {@code HashMap} instance from a stream (i.e.,
- * deserialize it).
+ * Reconstitutes this map from a stream (that is, deserializes it).
+ * @param s the stream
+ * @throws ClassNotFoundException if the class of a serialized object
+ * could not be found
+ * @throws IOException if an I/O error occurs
*/
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
@@ -1445,7 +1449,7 @@ public class HashMap extends AbstractMap
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
- Node[] tab = (Node[])new Node[cap];
+ Node[] tab = (Node[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
@@ -1830,7 +1834,7 @@ public class HashMap extends AbstractMap
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
Node[] tab;
if (size > 0 && (tab = table) != null) {
- for (Node e : tab) {
+ for (Node e : tab) {
for (; e != null; e = e.next) {
s.writeObject(e.key);
s.writeObject(e.value);
@@ -1951,7 +1955,6 @@ public class HashMap extends AbstractMap
/**
* Forms tree of the nodes linked from this node.
- * @return root of tree
*/
final void treeify(Node[] tab) {
TreeNode root = null;
@@ -2089,8 +2092,11 @@ public class HashMap extends AbstractMap
return;
if (root.parent != null)
root = root.root();
- if (root == null || root.right == null ||
- (rl = root.left) == null || rl.left == null) {
+ if (root == null
+ || (movable
+ && (root.right == null
+ || (rl = root.left) == null
+ || rl.left == null))) {
tab[index] = first.untreeify(map); // too small
return;
}
@@ -2319,7 +2325,7 @@ public class HashMap extends AbstractMap
static TreeNode balanceDeletion(TreeNode root,
TreeNode x) {
- for (TreeNode xp, xpl, xpr;;) {
+ for (TreeNode xp, xpl, xpr;;) {
if (x == null || x == root)
return root;
else if ((xp = x.parent) == null) {
diff --git a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java
index 9946b128591..66266827260 100644
--- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java
+++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java
@@ -2490,13 +2490,13 @@ public class CompletableFuture implements Future, CompletionStage {
for (Completion p = stack; p != null; p = p.next)
++count;
return super.toString() +
- ((r == null) ?
- ((count == 0) ?
- "[Not completed]" :
- "[Not completed, " + count + " dependents]") :
- (((r instanceof AltResult) && ((AltResult)r).ex != null) ?
- "[Completed exceptionally]" :
- "[Completed normally]"));
+ ((r == null)
+ ? ((count == 0)
+ ? "[Not completed]"
+ : "[Not completed, " + count + " dependents]")
+ : (((r instanceof AltResult) && ((AltResult)r).ex != null)
+ ? "[Completed exceptionally: " + ((AltResult)r).ex + "]"
+ : "[Completed normally]"));
}
// jdk9 additions
diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java
index e00ddd54afb..03ed2fa2974 100644
--- a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java
+++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java
@@ -1394,8 +1394,8 @@ public class ConcurrentHashMap extends AbstractMap
}
/**
- * Saves the state of the {@code ConcurrentHashMap} instance to a
- * stream (i.e., serializes it).
+ * Saves this map to a stream (that is, serializes it).
+ *
* @param s the stream
* @throws java.io.IOException if an I/O error occurs
* @serialData
@@ -1439,7 +1439,7 @@ public class ConcurrentHashMap extends AbstractMap
}
/**
- * Reconstitutes the instance from a stream (that is, deserializes it).
+ * Reconstitutes this map from a stream (that is, deserializes it).
* @param s the stream
* @throws ClassNotFoundException if the class of a serialized object
* could not be found
diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java
index 3fe50c3b951..96afa4c32c8 100644
--- a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java
+++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java
@@ -58,6 +58,7 @@ import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.concurrent.atomic.LongAdder;
/**
* A scalable concurrent {@link ConcurrentNavigableMap} implementation.
@@ -86,12 +87,7 @@ import java.util.function.Predicate;
* associated map using {@code put}, {@code putIfAbsent}, or
* {@code replace}, depending on exactly which effect you need.)
*
- * Beware that, unlike in most collections, the {@code size}
- * method is not a constant-time operation. Because of the
- * asynchronous nature of these maps, determining the current number
- * of elements requires a traversal of the elements, and so may report
- * inaccurate results if this collection is modified during traversal.
- * Additionally, the bulk operations {@code putAll}, {@code equals},
+ *
Beware that bulk operations {@code putAll}, {@code equals},
* {@code toArray}, {@code containsValue}, and {@code clear} are
* not guaranteed to be performed atomically. For example, an
* iterator operating concurrently with a {@code putAll} operation
@@ -158,42 +154,35 @@ public class ConcurrentSkipListMap extends AbstractMap
* be slow and space-intensive using AtomicMarkedReference), nodes
* use direct CAS'able next pointers. On deletion, instead of
* marking a pointer, they splice in another node that can be
- * thought of as standing for a marked pointer (indicating this by
- * using otherwise impossible field values). Using plain nodes
- * acts roughly like "boxed" implementations of marked pointers,
- * but uses new nodes only when nodes are deleted, not for every
- * link. This requires less space and supports faster
- * traversal. Even if marked references were better supported by
- * JVMs, traversal using this technique might still be faster
- * because any search need only read ahead one more node than
- * otherwise required (to check for trailing marker) rather than
- * unmasking mark bits or whatever on each read.
+ * thought of as standing for a marked pointer (see method
+ * unlinkNode). Using plain nodes acts roughly like "boxed"
+ * implementations of marked pointers, but uses new nodes only
+ * when nodes are deleted, not for every link. This requires less
+ * space and supports faster traversal. Even if marked references
+ * were better supported by JVMs, traversal using this technique
+ * might still be faster because any search need only read ahead
+ * one more node than otherwise required (to check for trailing
+ * marker) rather than unmasking mark bits or whatever on each
+ * read.
*
* This approach maintains the essential property needed in the HM
* algorithm of changing the next-pointer of a deleted node so
* that any other CAS of it will fail, but implements the idea by
- * changing the pointer to point to a different node, not by
- * marking it. While it would be possible to further squeeze
- * space by defining marker nodes not to have key/value fields, it
- * isn't worth the extra type-testing overhead. The deletion
- * markers are rarely encountered during traversal and are
- * normally quickly garbage collected. (Note that this technique
- * would not work well in systems without garbage collection.)
+ * changing the pointer to point to a different node (with
+ * otherwise illegal null fields), not by marking it. While it
+ * would be possible to further squeeze space by defining marker
+ * nodes not to have key/value fields, it isn't worth the extra
+ * type-testing overhead. The deletion markers are rarely
+ * encountered during traversal, are easily detected via null
+ * checks that are needed anyway, and are normally quickly garbage
+ * collected. (Note that this technique would not work well in
+ * systems without garbage collection.)
*
* In addition to using deletion markers, the lists also use
* nullness of value fields to indicate deletion, in a style
* similar to typical lazy-deletion schemes. If a node's value is
* null, then it is considered logically deleted and ignored even
- * though it is still reachable. This maintains proper control of
- * concurrent replace vs delete operations -- an attempted replace
- * must fail if a delete beat it by nulling field, and a delete
- * must return the last non-null value held in the field. (Note:
- * Null, rather than some special marker, is used for value fields
- * here because it just so happens to mesh with the Map API
- * requirement that method get returns null if there is no
- * mapping, which allows nodes to remain concurrently readable
- * even when deleted. Using any other marker value here would be
- * messy at best.)
+ * though it is still reachable.
*
* Here's the sequence of events for a deletion of node n with
* predecessor b and successor f, initially:
@@ -203,9 +192,8 @@ public class ConcurrentSkipListMap extends AbstractMap
* +------+ +------+ +------+
*
* 1. CAS n's value field from non-null to null.
- * From this point on, no public operations encountering
- * the node consider this mapping to exist. However, other
- * ongoing insertions and deletions might still modify
+ * Traversals encountering a node with null value ignore it.
+ * However, ongoing insertions and deletions might still modify
* n's next pointer.
*
* 2. CAS n's next pointer to point to a new marker node.
@@ -228,12 +216,7 @@ public class ConcurrentSkipListMap extends AbstractMap
* thread noticed during a traversal a node with null value and
* helped out by marking and/or unlinking. This helping-out
* ensures that no thread can become stuck waiting for progress of
- * the deleting thread. The use of marker nodes slightly
- * complicates helping-out code because traversals must track
- * consistent reads of up to four nodes (b, n, marker, f), not
- * just (b, n, f), although the next field of a marker is
- * immutable, and once a next field is CAS'ed to point to a
- * marker, it never again changes, so this requires less care.
+ * the deleting thread.
*
* Skip lists add indexing to this scheme, so that the base-level
* traversals start close to the locations being found, inserted
@@ -243,113 +226,101 @@ public class ConcurrentSkipListMap extends AbstractMap
* b) that are not (structurally) deleted, otherwise retrying
* after processing the deletion.
*
- * Index levels are maintained as lists with volatile next fields,
- * using CAS to link and unlink. Races are allowed in index-list
- * operations that can (rarely) fail to link in a new index node
- * or delete one. (We can't do this of course for data nodes.)
- * However, even when this happens, the index lists remain sorted,
- * so correctly serve as indices. This can impact performance,
- * but since skip lists are probabilistic anyway, the net result
- * is that under contention, the effective "p" value may be lower
- * than its nominal value. And race windows are kept small enough
- * that in practice these failures are rare, even under a lot of
- * contention.
+ * Index levels are maintained using CAS to link and unlink
+ * successors ("right" fields). Races are allowed in index-list
+ * operations that can (rarely) fail to link in a new index node.
+ * (We can't do this of course for data nodes.) However, even
+ * when this happens, the index lists correctly guide search.
+ * This can impact performance, but since skip lists are
+ * probabilistic anyway, the net result is that under contention,
+ * the effective "p" value may be lower than its nominal value.
*
- * The fact that retries (for both base and index lists) are
- * relatively cheap due to indexing allows some minor
- * simplifications of retry logic. Traversal restarts are
- * performed after most "helping-out" CASes. This isn't always
- * strictly necessary, but the implicit backoffs tend to help
- * reduce other downstream failed CAS's enough to outweigh restart
- * cost. This worsens the worst case, but seems to improve even
- * highly contended cases.
- *
- * Unlike most skip-list implementations, index insertion and
- * deletion here require a separate traversal pass occurring after
- * the base-level action, to add or remove index nodes. This adds
- * to single-threaded overhead, but improves contended
- * multithreaded performance by narrowing interference windows,
- * and allows deletion to ensure that all index nodes will be made
- * unreachable upon return from a public remove operation, thus
- * avoiding unwanted garbage retention. This is more important
- * here than in some other data structures because we cannot null
- * out node fields referencing user keys since they might still be
- * read by other ongoing traversals.
+ * Index insertion and deletion sometimes require a separate
+ * traversal pass occurring after the base-level action, to add or
+ * remove index nodes. This adds to single-threaded overhead, but
+ * improves contended multithreaded performance by narrowing
+ * interference windows, and allows deletion to ensure that all
+ * index nodes will be made unreachable upon return from a public
+ * remove operation, thus avoiding unwanted garbage retention.
*
* Indexing uses skip list parameters that maintain good search
* performance while using sparser-than-usual indices: The
- * hardwired parameters k=1, p=0.5 (see method doPut) mean
- * that about one-quarter of the nodes have indices. Of those that
- * do, half have one level, a quarter have two, and so on (see
- * Pugh's Skip List Cookbook, sec 3.4). The expected total space
- * requirement for a map is slightly less than for the current
- * implementation of java.util.TreeMap.
+ * hardwired parameters k=1, p=0.5 (see method doPut) mean that
+ * about one-quarter of the nodes have indices. Of those that do,
+ * half have one level, a quarter have two, and so on (see Pugh's
+ * Skip List Cookbook, sec 3.4), up to a maximum of 62 levels
+ * (appropriate for up to 2^63 elements). The expected total
+ * space requirement for a map is slightly less than for the
+ * current implementation of java.util.TreeMap.
*
* Changing the level of the index (i.e, the height of the
- * tree-like structure) also uses CAS. The head index has initial
- * level/height of one. Creation of an index with height greater
- * than the current level adds a level to the head index by
- * CAS'ing on a new top-most head. To maintain good performance
- * after a lot of removals, deletion methods heuristically try to
- * reduce the height if the topmost levels appear to be empty.
- * This may encounter races in which it possible (but rare) to
- * reduce and "lose" a level just as it is about to contain an
- * index (that will then never be encountered). This does no
- * structural harm, and in practice appears to be a better option
- * than allowing unrestrained growth of levels.
+ * tree-like structure) also uses CAS. Creation of an index with
+ * height greater than the current level adds a level to the head
+ * index by CAS'ing on a new top-most head. To maintain good
+ * performance after a lot of removals, deletion methods
+ * heuristically try to reduce the height if the topmost levels
+ * appear to be empty. This may encounter races in which it is
+ * possible (but rare) to reduce and "lose" a level just as it is
+ * about to contain an index (that will then never be
+ * encountered). This does no structural harm, and in practice
+ * appears to be a better option than allowing unrestrained growth
+ * of levels.
*
- * The code for all this is more verbose than you'd like. Most
- * operations entail locating an element (or position to insert an
- * element). The code to do this can't be nicely factored out
- * because subsequent uses require a snapshot of predecessor
- * and/or successor and/or value fields which can't be returned
- * all at once, at least not without creating yet another object
- * to hold them -- creating such little objects is an especially
- * bad idea for basic internal search operations because it adds
- * to GC overhead. (This is one of the few times I've wished Java
- * had macros.) Instead, some traversal code is interleaved within
- * insertion and removal operations. The control logic to handle
- * all the retry conditions is sometimes twisty. Most search is
- * broken into 2 parts. findPredecessor() searches index nodes
- * only, returning a base-level predecessor of the key. findNode()
- * finishes out the base-level search. Even with this factoring,
- * there is a fair amount of near-duplication of code to handle
- * variants.
+ * This class provides concurrent-reader-style memory consistency,
+ * ensuring that read-only methods report status and/or values no
+ * staler than those holding at method entry. This is done by
+ * performing all publication and structural updates using
+ * (volatile) CAS, placing an acquireFence in a few access
+ * methods, and ensuring that linked objects are transitively
+ * acquired via dependent reads (normally once) unless performing
+ * a volatile-mode CAS operation (that also acts as an acquire and
+ * release). This form of fence-hoisting is similar to RCU and
+ * related techniques (see McKenney's online book
+ * https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html)
+ * It minimizes overhead that may otherwise occur when using so
+ * many volatile-mode reads. Using explicit acquireFences is
+ * logistically easier than targeting particular fields to be read
+ * in acquire mode: fences are just hoisted up as far as possible,
+ * to the entry points or loop headers of a few methods. A
+ * potential disadvantage is that these few remaining fences are
+ * not easily optimized away by compilers under exclusively
+ * single-thread use. It requires some care to avoid volatile
+ * mode reads of other fields. (Note that the memory semantics of
+ * a reference dependently read in plain mode exactly once are
+ * equivalent to those for atomic opaque mode.) Iterators and
+ * other traversals encounter each node and value exactly once.
+ * Other operations locate an element (or position to insert an
+ * element) via a sequence of dereferences. This search is broken
+ * into two parts. Method findPredecessor (and its specialized
+ * embeddings) searches index nodes only, returning a base-level
+ * predecessor of the key. Callers carry out the base-level
+ * search, restarting if encountering a marker preventing link
+ * modification. In some cases, it is possible to encounter a
+ * node multiple times while descending levels. For mutative
+ * operations, the reported value is validated using CAS (else
+ * retrying), preserving linearizability with respect to each
+ * other. Others may return any (non-null) value holding in the
+ * course of the method call. (Search-based methods also include
+ * some useless-looking explicit null checks designed to allow
+ * more fields to be nulled out upon removal, to reduce floating
+ * garbage, but which is not currently done, pending discovery of
+ * a way to do this with less impact on other operations.)
*
* To produce random values without interference across threads,
* we use within-JDK thread local random support (via the
* "secondary seed", to avoid interference with user-level
* ThreadLocalRandom.)
*
- * A previous version of this class wrapped non-comparable keys
- * with their comparators to emulate Comparables when using
- * comparators vs Comparables. However, JVMs now appear to better
- * handle infusing comparator-vs-comparable choice into search
- * loops. Static method cpr(comparator, x, y) is used for all
- * comparisons, which works well as long as the comparator
- * argument is set up outside of loops (thus sometimes passed as
- * an argument to internal methods) to avoid field re-reads.
- *
* For explanation of algorithms sharing at least a couple of
* features with this one, see Mikhail Fomitchev's thesis
* (http://www.cs.yorku.ca/~mikhail/), Keir Fraser's thesis
* (http://www.cl.cam.ac.uk/users/kaf24/), and Hakan Sundell's
* thesis (http://www.cs.chalmers.se/~phs/).
*
- * Given the use of tree-like index nodes, you might wonder why
- * this doesn't use some kind of search tree instead, which would
- * support somewhat faster search operations. The reason is that
- * there are no known efficient lock-free insertion and deletion
- * algorithms for search trees. The immutability of the "down"
- * links of index nodes (as opposed to mutable "left" fields in
- * true trees) makes this tractable using only CAS operations.
- *
* Notation guide for local variables
- * Node: b, n, f for predecessor, node, successor
+ * Node: b, n, f, p for predecessor, node, successor, aux
* Index: q, r, d for index node, right, down.
- * t for another index node
* Head: h
- * Levels: j
* Keys: k, key
* Values: v, value
* Comparisons: c
@@ -357,16 +328,6 @@ public class ConcurrentSkipListMap extends AbstractMap
private static final long serialVersionUID = -8627078645895051609L;
- /**
- * Special value used to identify base-level header.
- */
- static final Object BASE_HEADER = new Object();
-
- /**
- * The topmost head index of the skiplist.
- */
- private transient volatile HeadIndex head;
-
/**
* The comparator used to maintain order in this map, or null if
* using natural ordering. (Non-private to simplify access in
@@ -375,311 +336,152 @@ public class ConcurrentSkipListMap extends AbstractMap
*/
final Comparator super K> comparator;
+ /** Lazily initialized topmost index of the skiplist. */
+ private transient Index head;
+ /** Lazily initialized element count */
+ private transient LongAdder adder;
/** Lazily initialized key set */
private transient KeySet keySet;
/** Lazily initialized values collection */
private transient Values values;
/** Lazily initialized entry set */
private transient EntrySet entrySet;
- /** Lazily initialized descending key set */
+ /** Lazily initialized descending map */
private transient SubMap descendingMap;
- /**
- * Initializes or resets state. Needed by constructors, clone,
- * clear, readObject. and ConcurrentSkipListSet.clone.
- * (Note that comparator must be separately initialized.)
- */
- private void initialize() {
- keySet = null;
- entrySet = null;
- values = null;
- descendingMap = null;
- head = new HeadIndex(new Node(null, BASE_HEADER, null),
- null, null, 1);
- }
-
- /**
- * compareAndSet head node.
- */
- private boolean casHead(HeadIndex cmp, HeadIndex val) {
- return HEAD.compareAndSet(this, cmp, val);
- }
-
- /* ---------------- Nodes -------------- */
-
/**
* Nodes hold keys and values, and are singly linked in sorted
* order, possibly with some intervening marker nodes. The list is
- * headed by a dummy node accessible as head.node. The value field
- * is declared only as Object because it takes special non-V
- * values for marker and header nodes.
+ * headed by a header node accessible as head.node. Headers and
+ * marker nodes have null keys. The val field (but currently not
+ * the key field) is nulled out upon deletion.
*/
static final class Node {
- final K key;
- volatile Object value;
- volatile Node next;
-
- /**
- * Creates a new regular node.
- */
- Node(K key, Object value, Node next) {
+ final K key; // currently, never detached
+ V val;
+ Node next;
+ Node(K key, V value, Node next) {
this.key = key;
- this.value = value;
+ this.val = value;
this.next = next;
}
-
- /**
- * Creates a new marker node. A marker is distinguished by
- * having its value field point to itself. Marker nodes also
- * have null keys, a fact that is exploited in a few places,
- * but this doesn't distinguish markers from the base-level
- * header node (head.node), which also has a null key.
- */
- Node(Node next) {
- this.key = null;
- this.value = this;
- this.next = next;
- }
-
- /**
- * compareAndSet value field.
- */
- boolean casValue(Object cmp, Object val) {
- return VALUE.compareAndSet(this, cmp, val);
- }
-
- /**
- * compareAndSet next field.
- */
- boolean casNext(Node cmp, Node val) {
- return NEXT.compareAndSet(this, cmp, val);
- }
-
- /**
- * Returns true if this node is a marker. This method isn't
- * actually called in any current code checking for markers
- * because callers will have already read value field and need
- * to use that read (not another done here) and so directly
- * test if value points to node.
- *
- * @return true if this node is a marker node
- */
- boolean isMarker() {
- return value == this;
- }
-
- /**
- * Returns true if this node is the header of base-level list.
- * @return true if this node is header node
- */
- boolean isBaseHeader() {
- return value == BASE_HEADER;
- }
-
- /**
- * Tries to append a deletion marker to this node.
- * @param f the assumed current successor of this node
- * @return true if successful
- */
- boolean appendMarker(Node f) {
- return casNext(f, new Node(f));
- }
-
- /**
- * Helps out a deletion by appending marker or unlinking from
- * predecessor. This is called during traversals when value
- * field seen to be null.
- * @param b predecessor
- * @param f successor
- */
- void helpDelete(Node b, Node f) {
- /*
- * Rechecking links and then doing only one of the
- * help-out stages per call tends to minimize CAS
- * interference among helping threads.
- */
- if (f == next && this == b.next) {
- if (f == null || f.value != f) // not already marked
- casNext(f, new Node(f));
- else
- b.casNext(this, f.next);
- }
- }
-
- /**
- * Returns value if this node contains a valid key-value pair,
- * else null.
- * @return this node's value if it isn't a marker or header or
- * is deleted, else null
- */
- V getValidValue() {
- Object v = value;
- if (v == this || v == BASE_HEADER)
- return null;
- @SuppressWarnings("unchecked") V vv = (V)v;
- return vv;
- }
-
- /**
- * Creates and returns a new SimpleImmutableEntry holding current
- * mapping if this node holds a valid value, else null.
- * @return new entry or null
- */
- AbstractMap.SimpleImmutableEntry createSnapshot() {
- Object v = value;
- if (v == null || v == this || v == BASE_HEADER)
- return null;
- @SuppressWarnings("unchecked") V vv = (V)v;
- return new AbstractMap.SimpleImmutableEntry(key, vv);
- }
-
- // VarHandle mechanics
- private static final VarHandle VALUE;
- private static final VarHandle NEXT;
- static {
- try {
- MethodHandles.Lookup l = MethodHandles.lookup();
- VALUE = l.findVarHandle(Node.class, "value", Object.class);
- NEXT = l.findVarHandle(Node.class, "next", Node.class);
- } catch (ReflectiveOperationException e) {
- throw new Error(e);
- }
- }
}
- /* ---------------- Indexing -------------- */
-
/**
- * Index nodes represent the levels of the skip list. Note that
- * even though both Nodes and Indexes have forward-pointing
- * fields, they have different types and are handled in different
- * ways, that can't nicely be captured by placing field in a
- * shared abstract class.
+ * Index nodes represent the levels of the skip list.
*/
- static class Index {
- final Node node;
+ static final class Index {
+ final Node node; // currently, never detached
final Index down;
- volatile Index right;
-
- /**
- * Creates index node with given values.
- */
+ Index right;
Index(Node node, Index down, Index right) {
this.node = node;
this.down = down;
this.right = right;
}
-
- /**
- * compareAndSet right field.
- */
- final boolean casRight(Index cmp, Index val) {
- return RIGHT.compareAndSet(this, cmp, val);
- }
-
- /**
- * Returns true if the node this indexes has been deleted.
- * @return true if indexed node is known to be deleted
- */
- final boolean indexesDeletedNode() {
- return node.value == null;
- }
-
- /**
- * Tries to CAS newSucc as successor. To minimize races with
- * unlink that may lose this index node, if the node being
- * indexed is known to be deleted, it doesn't try to link in.
- * @param succ the expected current successor
- * @param newSucc the new successor
- * @return true if successful
- */
- final boolean link(Index succ, Index newSucc) {
- Node n = node;
- newSucc.right = succ;
- return n.value != null && casRight(succ, newSucc);
- }
-
- /**
- * Tries to CAS right field to skip over apparent successor
- * succ. Fails (forcing a retraversal by caller) if this node
- * is known to be deleted.
- * @param succ the expected current successor
- * @return true if successful
- */
- final boolean unlink(Index succ) {
- return node.value != null && casRight(succ, succ.right);
- }
-
- // VarHandle mechanics
- private static final VarHandle RIGHT;
- static {
- try {
- MethodHandles.Lookup l = MethodHandles.lookup();
- RIGHT = l.findVarHandle(Index.class, "right", Index.class);
- } catch (ReflectiveOperationException e) {
- throw new Error(e);
- }
- }
}
- /* ---------------- Head nodes -------------- */
-
- /**
- * Nodes heading each level keep track of their level.
- */
- static final class HeadIndex extends Index {
- final int level;
- HeadIndex(Node node, Index down, Index right, int level) {
- super(node, down, right);
- this.level = level;
- }
- }
-
- /* ---------------- Comparison utilities -------------- */
+ /* ---------------- Utilities -------------- */
/**
* Compares using comparator or natural ordering if null.
* Called only by methods that have performed required type checks.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
- static final int cpr(Comparator c, Object x, Object y) {
+ static int cpr(Comparator c, Object x, Object y) {
return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y);
}
+ /**
+ * Returns the header for base node list, or null if uninitialized
+ */
+ final Node baseHead() {
+ Index h;
+ VarHandle.acquireFence();
+ return ((h = head) == null) ? null : h.node;
+ }
+
+ /**
+ * Tries to unlink deleted node n from predecessor b (if both
+ * exist), by first splicing in a marker if not already present.
+ * Upon return, node n is sure to be unlinked from b, possibly
+ * via the actions of some other thread.
+ *
+ * @param b if nonnull, predecessor
+ * @param n if nonnull, node known to be deleted
+ */
+ static void unlinkNode(Node b, Node n) {
+ if (b != null && n != null) {
+ Node f, p;
+ for (;;) {
+ if ((f = n.next) != null && f.key == null) {
+ p = f.next; // already marked
+ break;
+ }
+ else if (NEXT.compareAndSet(n, f,
+ new Node(null, null, f))) {
+ p = f; // add marker
+ break;
+ }
+ }
+ NEXT.compareAndSet(b, n, p);
+ }
+ }
+
+ /**
+ * Adds to element count, initializing adder if necessary
+ *
+ * @param c count to add
+ */
+ private void addCount(long c) {
+ LongAdder a;
+ do {} while ((a = adder) == null &&
+ !ADDER.compareAndSet(this, null, a = new LongAdder()));
+ a.add(c);
+ }
+
+ /**
+ * Returns element count, initializing adder if necessary.
+ */
+ final long getAdderCount() {
+ LongAdder a; long c;
+ do {} while ((a = adder) == null &&
+ !ADDER.compareAndSet(this, null, a = new LongAdder()));
+ return ((c = a.sum()) <= 0L) ? 0L : c; // ignore transient negatives
+ }
+
/* ---------------- Traversal -------------- */
/**
- * Returns a base-level node with key strictly less than given key,
- * or the base-level header if there is no such node. Also
- * unlinks indexes to deleted nodes found along the way. Callers
- * rely on this side-effect of clearing indices to deleted nodes.
- * @param key the key
- * @return a predecessor of key
+ * Returns an index node with key strictly less than given key.
+ * Also unlinks indexes to deleted nodes found along the way.
+ * Callers rely on this side-effect of clearing indices to deleted
+ * nodes.
+ *
+ * @param key if nonnull the key
+ * @return a predecessor node of key, or null if uninitialized or null key
*/
private Node findPredecessor(Object key, Comparator super K> cmp) {
- if (key == null)
- throw new NullPointerException(); // don't postpone errors
- for (;;) {
- for (Index q = head, r = q.right, d;;) {
- if (r != null) {
- Node n = r.node;
- K k = n.key;
- if (n.value == null) {
- if (!q.unlink(r))
- break; // restart
- r = q.right; // reread r
- continue;
- }
- if (cpr(cmp, key, k) > 0) {
+ Index q;
+ VarHandle.acquireFence();
+ if ((q = head) == null || key == null)
+ return null;
+ else {
+ for (Index r, d;;) {
+ while ((r = q.right) != null) {
+ Node p; K k;
+ if ((p = r.node) == null || (k = p.key) == null ||
+ p.val == null) // unlink index to deleted node
+ RIGHT.compareAndSet(q, r, r.right);
+ else if (cpr(cmp, key, k) > 0)
q = r;
- r = r.right;
- continue;
- }
+ else
+ break;
}
- if ((d = q.down) == null)
+ if ((d = q.down) != null)
+ q = d;
+ else
return q.node;
- q = d;
- r = d.right;
}
}
}
@@ -689,41 +491,11 @@ public class ConcurrentSkipListMap extends AbstractMap
* deleted nodes seen along the way. Repeatedly traverses at
* base-level looking for key starting at predecessor returned
* from findPredecessor, processing base-level deletions as
- * encountered. Some callers rely on this side-effect of clearing
- * deleted nodes.
- *
- * Restarts occur, at traversal step centered on node n, if:
- *
- * (1) After reading n's next field, n is no longer assumed
- * predecessor b's current successor, which means that
- * we don't have a consistent 3-node snapshot and so cannot
- * unlink any subsequent deleted nodes encountered.
- *
- * (2) n's value field is null, indicating n is deleted, in
- * which case we help out an ongoing structural deletion
- * before retrying. Even though there are cases where such
- * unlinking doesn't require restart, they aren't sorted out
- * here because doing so would not usually outweigh cost of
- * restarting.
- *
- * (3) n is a marker or n's predecessor's value field is null,
- * indicating (among other possibilities) that
- * findPredecessor returned a deleted node. We can't unlink
- * the node because we don't know its predecessor, so rely
- * on another call to findPredecessor to notice and return
- * some earlier predecessor, which it will do. This check is
- * only strictly needed at beginning of loop, (and the
- * b.value check isn't strictly needed at all) but is done
- * each iteration to help avoid contention with other
- * threads by callers that will fail to be able to change
- * links, and so will retry anyway.
- *
- * The traversal loops in doPut, doRemove, and findNear all
- * include the same three kinds of checks. And specialized
- * versions appear in findFirst, and findLast and their variants.
- * They can't easily share code because each uses the reads of
- * fields held in locals occurring in the orders they were
- * performed.
+ * encountered. Restarts occur, at traversal step encountering
+ * node n, if n's key field is null, indicating it is a marker, so
+ * its predecessor is deleted before continuing, which we help do
+ * by re-finding a valid predecessor. The traversal loops in
+ * doPut, doRemove, and findNear all include the same checks.
*
* @param key the key
* @return node holding key, or null if no such
@@ -732,67 +504,81 @@ public class ConcurrentSkipListMap extends AbstractMap
if (key == null)
throw new NullPointerException(); // don't postpone errors
Comparator super K> cmp = comparator;
- outer: for (;;) {
- for (Node b = findPredecessor(key, cmp), n = b.next;;) {
- Object v; int c;
- if (n == null)
- break outer;
- Node f = n.next;
- if (n != b.next) // inconsistent read
- break;
- if ((v = n.value) == null) { // n is deleted
- n.helpDelete(b, f);
- break;
- }
- if (b.value == null || v == n) // b is deleted
- break;
- if ((c = cpr(cmp, key, n.key)) == 0)
+ Node b;
+ outer: while ((b = findPredecessor(key, cmp)) != null) {
+ for (;;) {
+ Node n; K k; V v; int c;
+ if ((n = b.next) == null)
+ break outer; // empty
+ else if ((k = n.key) == null)
+ break; // b is deleted
+ else if ((v = n.val) == null)
+ unlinkNode(b, n); // n is deleted
+ else if ((c = cpr(cmp, key, k)) > 0)
+ b = n;
+ else if (c == 0)
return n;
- if (c < 0)
+ else
break outer;
- b = n;
- n = f;
}
}
return null;
}
/**
- * Gets value for key. Almost the same as findNode, but returns
- * the found value (to avoid retries during re-reads)
+ * Gets value for key. Same idea as findNode, except skips over
+ * deletions and markers, and returns first encountered value to
+ * avoid possibly inconsistent rereads.
*
* @param key the key
* @return the value, or null if absent
*/
private V doGet(Object key) {
+ Index q;
+ VarHandle.acquireFence();
if (key == null)
throw new NullPointerException();
Comparator super K> cmp = comparator;
- outer: for (;;) {
- for (Node b = findPredecessor(key, cmp), n = b.next;;) {
- Object v; int c;
- if (n == null)
- break outer;
- Node f = n.next;
- if (n != b.next) // inconsistent read
- break;
- if ((v = n.value) == null) { // n is deleted
- n.helpDelete(b, f);
+ V result = null;
+ if ((q = head) != null) {
+ outer: for (Index r, d;;) {
+ while ((r = q.right) != null) {
+ Node p; K k; V v; int c;
+ if ((p = r.node) == null || (k = p.key) == null ||
+ (v = p.val) == null)
+ RIGHT.compareAndSet(q, r, r.right);
+ else if ((c = cpr(cmp, key, k)) > 0)
+ q = r;
+ else if (c == 0) {
+ result = v;
+ break outer;
+ }
+ else
+ break;
+ }
+ if ((d = q.down) != null)
+ q = d;
+ else {
+ Node b, n;
+ if ((b = q.node) != null) {
+ while ((n = b.next) != null) {
+ V v; int c;
+ K k = n.key;
+ if ((v = n.val) == null || k == null ||
+ (c = cpr(cmp, key, k)) > 0)
+ b = n;
+ else {
+ if (c == 0)
+ result = v;
+ break;
+ }
+ }
+ }
break;
}
- if (b.value == null || v == n) // b is deleted
- break;
- if ((c = cpr(cmp, key, n.key)) == 0) {
- @SuppressWarnings("unchecked") V vv = (V)v;
- return vv;
- }
- if (c < 0)
- break outer;
- b = n;
- n = f;
}
}
- return null;
+ return result;
}
/* ---------------- Insertion -------------- */
@@ -800,129 +586,160 @@ public class ConcurrentSkipListMap extends AbstractMap
/**
* Main insertion method. Adds element if not present, or
* replaces value if present and onlyIfAbsent is false.
+ *
* @param key the key
* @param value the value that must be associated with key
* @param onlyIfAbsent if should not insert if already present
* @return the old value, or null if newly inserted
*/
private V doPut(K key, V value, boolean onlyIfAbsent) {
- Node z; // added node
if (key == null)
throw new NullPointerException();
Comparator super K> cmp = comparator;
- outer: for (;;) {
- for (Node b = findPredecessor(key, cmp), n = b.next;;) {
- if (n != null) {
- Object v; int c;
- Node f = n.next;
- if (n != b.next) // inconsistent read
- break;
- if ((v = n.value) == null) { // n is deleted
- n.helpDelete(b, f);
- break;
- }
- if (b.value == null || v == n) // b is deleted
- break;
- if ((c = cpr(cmp, key, n.key)) > 0) {
- b = n;
- n = f;
- continue;
- }
- if (c == 0) {
- if (onlyIfAbsent || n.casValue(v, value)) {
- @SuppressWarnings("unchecked") V vv = (V)v;
- return vv;
- }
- break; // restart if lost race to replace value
- }
- // else c < 0; fall through
- } else if (b == head.node) {
- // map is empty, so type check key now
- cpr(cmp, key, key);
- }
-
- z = new Node(key, value, n);
- if (!b.casNext(n, z))
- break; // restart if lost race to append to b
- break outer;
+ for (;;) {
+ Index h; Node b;
+ VarHandle.acquireFence();
+ int levels = 0; // number of levels descended
+ if ((h = head) == null) { // try to initialize
+ Node base = new Node(null, null, null);
+ h = new Index(base, null, null);
+ b = (HEAD.compareAndSet(this, null, h)) ? base : null;
}
- }
-
- int rnd = ThreadLocalRandom.nextSecondarySeed();
- if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
- int level = 1, max;
- while (((rnd >>>= 1) & 1) != 0)
- ++level;
- Index idx = null;
- HeadIndex h = head;
- if (level <= (max = h.level)) {
- for (int i = 1; i <= level; ++i)
- idx = new Index(z, idx, null);
- }
- else { // try to grow by one level
- level = max + 1; // hold in array and later pick the one to use
- @SuppressWarnings("unchecked")Index[] idxs =
- (Index[])new Index,?>[level+1];
- for (int i = 1; i <= level; ++i)
- idxs[i] = idx = new Index(z, idx, null);
- for (;;) {
- h = head;
- int oldLevel = h.level;
- if (level <= oldLevel) // lost race to add level
- break;
- HeadIndex newh = h;
- Node oldbase = h.node;
- for (int j = oldLevel+1; j <= level; ++j)
- newh = new HeadIndex(oldbase, newh, idxs[j], j);
- if (casHead(h, newh)) {
- h = newh;
- idx = idxs[level = oldLevel];
- break;
- }
- }
- }
- // find insertion points and splice in
- splice: for (int insertionLevel = level;;) {
- int j = h.level;
- for (Index q = h, r = q.right, t = idx;;) {
- if (q == null || t == null)
- break splice;
- if (r != null) {
- Node n = r.node;
- // compare before deletion check avoids needing recheck
- int c = cpr(cmp, key, n.key);
- if (n.value == null) {
- if (!q.unlink(r))
- break;
- r = q.right;
- continue;
- }
- if (c > 0) {
+ else {
+ for (Index q = h, r, d;;) { // count while descending
+ while ((r = q.right) != null) {
+ Node p; K k;
+ if ((p = r.node) == null || (k = p.key) == null ||
+ p.val == null)
+ RIGHT.compareAndSet(q, r, r.right);
+ else if (cpr(cmp, key, k) > 0)
q = r;
- r = r.right;
- continue;
- }
+ else
+ break;
}
-
- if (j == insertionLevel) {
- if (!q.link(r, t))
- break; // restart
- if (t.node.value == null) {
- findNode(key);
- break splice;
- }
- if (--insertionLevel == 0)
- break splice;
+ if ((d = q.down) != null) {
+ ++levels;
+ q = d;
}
+ else {
+ b = q.node;
+ break;
+ }
+ }
+ }
+ if (b != null) {
+ Node z = null; // new node, if inserted
+ for (;;) { // find insertion point
+ Node n, p; K k; V v; int c;
+ if ((n = b.next) == null) {
+ if (b.key == null) // if empty, type check key now
+ cpr(cmp, key, key);
+ c = -1;
+ }
+ else if ((k = n.key) == null)
+ break; // can't append; restart
+ else if ((v = n.val) == null) {
+ unlinkNode(b, n);
+ c = 1;
+ }
+ else if ((c = cpr(cmp, key, k)) > 0)
+ b = n;
+ else if (c == 0 &&
+ (onlyIfAbsent || VAL.compareAndSet(n, v, value)))
+ return v;
- if (--j >= insertionLevel && j < level)
- t = t.down;
- q = q.down;
- r = q.right;
+ if (c < 0 &&
+ NEXT.compareAndSet(b, n,
+ p = new Node(key, value, n))) {
+ z = p;
+ break;
+ }
+ }
+
+ if (z != null) {
+ int lr = ThreadLocalRandom.nextSecondarySeed();
+ if ((lr & 0x3) == 0) { // add indices with 1/4 prob
+ int hr = ThreadLocalRandom.nextSecondarySeed();
+ long rnd = ((long)hr << 32) | ((long)lr & 0xffffffffL);
+ int skips = levels; // levels to descend before add
+ Index x = null;
+ for (;;) { // create at most 62 indices
+ x = new Index(z, x, null);
+ if (rnd >= 0L || --skips < 0)
+ break;
+ else
+ rnd <<= 1;
+ }
+ if (addIndices(h, skips, x, cmp) && skips < 0 &&
+ head == h) { // try to add new level
+ Index hx = new Index(z, x, null);
+ Index nh = new Index(h.node, h, hx);
+ HEAD.compareAndSet(this, h, nh);
+ }
+ if (z.val == null) // deleted while adding indices
+ findPredecessor(key, cmp); // clean
+ }
+ addCount(1L);
+ return null;
}
}
}
- return null;
+ }
+
+ /**
+ * Add indices after an insertion. Descends iteratively to the
+ * highest level of insertion, then recursively, to chain index
+ * nodes to lower ones. Returns null on (staleness) failure,
+ * disabling higher-level insertions. Recursion depths are
+ * exponentially less probable.
+ *
+ * @param q starting index for current level
+ * @param skips levels to skip before inserting
+ * @param x index for this insertion
+ * @param cmp comparator
+ */
+ static boolean addIndices(Index q, int skips, Index x,
+ Comparator super K> cmp) {
+ Node z; K key;
+ if (x != null && (z = x.node) != null && (key = z.key) != null &&
+ q != null) { // hoist checks
+ boolean retrying = false;
+ for (;;) { // find splice point
+ Index r, d; int c;
+ if ((r = q.right) != null) {
+ Node p; K k;
+ if ((p = r.node) == null || (k = p.key) == null ||
+ p.val == null) {
+ RIGHT.compareAndSet(q, r, r.right);
+ c = 0;
+ }
+ else if ((c = cpr(cmp, key, k)) > 0)
+ q = r;
+ else if (c == 0)
+ break; // stale
+ }
+ else
+ c = -1;
+
+ if (c < 0) {
+ if ((d = q.down) != null && skips > 0) {
+ --skips;
+ q = d;
+ }
+ else if (d != null && !retrying &&
+ !addIndices(d, 0, x.down, cmp))
+ break;
+ else {
+ x.right = r;
+ if (RIGHT.compareAndSet(q, r, x))
+ return true;
+ else
+ retrying = true; // re-find splice point
+ }
+ }
+ }
+ }
+ return false;
}
/* ---------------- Deletion -------------- */
@@ -932,15 +749,6 @@ public class ConcurrentSkipListMap extends AbstractMap
* deletion marker, unlinks predecessor, removes associated index
* nodes, and possibly reduces head index level.
*
- * Index nodes are cleared out simply by calling findPredecessor.
- * which unlinks indexes to deleted nodes found along path to key,
- * which will include the indexes to this node. This is done
- * unconditionally. We can't check beforehand whether there are
- * index nodes because it might be the case that some or all
- * indexes hadn't been inserted yet for this node during initial
- * search for it, and we'd like to ensure lack of garbage
- * retention, so must call to be sure.
- *
* @param key the key
* @param value if non-null, the value that must be
* associated with key
@@ -950,43 +758,36 @@ public class ConcurrentSkipListMap extends AbstractMap
if (key == null)
throw new NullPointerException();
Comparator super K> cmp = comparator;
- outer: for (;;) {
- for (Node b = findPredecessor(key, cmp), n = b.next;;) {
- Object v; int c;
- if (n == null)
+ V result = null;
+ Node b;
+ outer: while ((b = findPredecessor(key, cmp)) != null &&
+ result == null) {
+ for (;;) {
+ Node n; K k; V v; int c;
+ if ((n = b.next) == null)
break outer;
- Node f = n.next;
- if (n != b.next) // inconsistent read
+ else if ((k = n.key) == null)
break;
- if ((v = n.value) == null) { // n is deleted
- n.helpDelete(b, f);
- break;
- }
- if (b.value == null || v == n) // b is deleted
- break;
- if ((c = cpr(cmp, key, n.key)) < 0)
- break outer;
- if (c > 0) {
+ else if ((v = n.val) == null)
+ unlinkNode(b, n);
+ else if ((c = cpr(cmp, key, k)) > 0)
b = n;
- n = f;
- continue;
- }
- if (value != null && !value.equals(v))
+ else if (c < 0)
break outer;
- if (!n.casValue(v, null))
- break;
- if (!n.appendMarker(f) || !b.casNext(n, f))
- findNode(key); // retry via findNode
- else {
- findPredecessor(key, cmp); // clean index
- if (head.right == null)
- tryReduceLevel();
+ else if (value != null && !value.equals(v))
+ break outer;
+ else if (VAL.compareAndSet(n, v, null)) {
+ result = v;
+ unlinkNode(b, n);
+ break; // loop to clean up
}
- @SuppressWarnings("unchecked") V vv = (V)v;
- return vv;
}
}
- return null;
+ if (result != null) {
+ tryReduceLevel();
+ addCount(-1L);
+ }
+ return result;
}
/**
@@ -1010,125 +811,71 @@ public class ConcurrentSkipListMap extends AbstractMap
* reduction.
*/
private void tryReduceLevel() {
- HeadIndex h = head;
- HeadIndex d;
- HeadIndex e;
- if (h.level > 3 &&
- (d = (HeadIndex)h.down) != null &&
- (e = (HeadIndex)d.down) != null &&
- e.right == null &&
- d.right == null &&
- h.right == null &&
- casHead(h, d) && // try to set
- h.right != null) // recheck
- casHead(d, h); // try to backout
+ Index h, d, e;
+ if ((h = head) != null && h.right == null &&
+ (d = h.down) != null && d.right == null &&
+ (e = d.down) != null && e.right == null &&
+ HEAD.compareAndSet(this, h, d) &&
+ h.right != null) // recheck
+ HEAD.compareAndSet(this, d, h); // try to backout
}
/* ---------------- Finding and removing first element -------------- */
/**
- * Specialized variant of findNode to get first valid node.
+ * Gets first valid node, unlinking deleted nodes if encountered.
* @return first node or null if empty
*/
final Node findFirst() {
- for (Node b, n;;) {
- if ((n = (b = head.node).next) == null)
- return null;
- if (n.value != null)
- return n;
- n.helpDelete(b, n.next);
+ Node b, n;
+ if ((b = baseHead()) != null) {
+ while ((n = b.next) != null) {
+ if (n.val == null)
+ unlinkNode(b, n);
+ else
+ return n;
+ }
}
+ return null;
+ }
+
+ /**
+ * Entry snapshot version of findFirst
+ */
+ final AbstractMap.SimpleImmutableEntry findFirstEntry() {
+ Node b, n; V v;
+ if ((b = baseHead()) != null) {
+ while ((n = b.next) != null) {
+ if ((v = n.val) == null)
+ unlinkNode(b, n);
+ else
+ return new AbstractMap.SimpleImmutableEntry(n.key, v);
+ }
+ }
+ return null;
}
/**
* Removes first entry; returns its snapshot.
* @return null if empty, else snapshot of first entry
*/
- private Map.Entry doRemoveFirstEntry() {
- for (Node b, n;;) {
- if ((n = (b = head.node).next) == null)
- return null;
- Node f = n.next;
- if (n != b.next)
- continue;
- Object v = n.value;
- if (v == null) {
- n.helpDelete(b, f);
- continue;
- }
- if (!n.casValue(v, null))
- continue;
- if (!n.appendMarker(f) || !b.casNext(n, f))
- findFirst(); // retry
- clearIndexToFirst();
- @SuppressWarnings("unchecked") V vv = (V)v;
- return new AbstractMap.SimpleImmutableEntry(n.key, vv);
- }
- }
-
- /**
- * Clears out index nodes associated with deleted first entry.
- */
- private void clearIndexToFirst() {
- for (;;) {
- for (Index q = head;;) {
- Index r = q.right;
- if (r != null && r.indexesDeletedNode() && !q.unlink(r))
- break;
- if ((q = q.down) == null) {
- if (head.right == null)
+ private AbstractMap.SimpleImmutableEntry doRemoveFirstEntry() {
+ Node b, n; V v;
+ if ((b = baseHead()) != null) {
+ while ((n = b.next) != null) {
+ if ((v = n.val) == null || VAL.compareAndSet(n, v, null)) {
+ K k = n.key;
+ unlinkNode(b, n);
+ if (v != null) {
tryReduceLevel();
- return;
+ findPredecessor(k, comparator); // clean index
+ addCount(-1L);
+ return new AbstractMap.SimpleImmutableEntry(k, v);
+ }
}
}
}
- }
-
- /**
- * Removes last entry; returns its snapshot.
- * Specialized variant of doRemove.
- * @return null if empty, else snapshot of last entry
- */
- private Map.Entry doRemoveLastEntry() {
- for (;;) {
- Node b = findPredecessorOfLast();
- Node n = b.next;
- if (n == null) {
- if (b.isBaseHeader()) // empty
- return null;
- else
- continue; // all b's successors are deleted; retry
- }
- for (;;) {
- Node