2626
2727package java .lang ;
2828
29+ import java .lang .ref .Reference ;
2930import java .util .NoSuchElementException ;
3031import java .util .Objects ;
31- import java .lang .ref .Reference ;
32- import java .util .concurrent .StructuredTaskScope ;
3332import java .util .concurrent .StructureViolationException ;
33+ import java .util .concurrent .StructuredTaskScope ;
34+ import java .util .function .IntSupplier ;
3435import java .util .function .Supplier ;
3536import jdk .internal .access .JavaUtilConcurrentTLRAccess ;
3637import jdk .internal .access .SharedSecrets ;
38+ import jdk .internal .vm .ScopedValueContainer ;
3739import jdk .internal .vm .annotation .ForceInline ;
3840import jdk .internal .vm .annotation .Hidden ;
39- import jdk .internal .vm .ScopedValueContainer ;
41+ import jdk .internal .vm .annotation . Stable ;
4042
4143/**
4244 * A value that may be safely and efficiently shared to methods without using method
@@ -244,6 +246,9 @@ public final class ScopedValue<T> {
244246 @ Override
245247 public int hashCode () { return hash ; }
246248
249+ @ Stable
250+ static IntSupplier hashGenerator ;
251+
247252 /**
248253 * An immutable map from {@code ScopedValue} to values.
249254 *
@@ -526,7 +531,8 @@ public static <T> Carrier where(ScopedValue<T> key, T value) {
526531 }
527532
528533 private ScopedValue () {
529- this .hash = generateKey ();
534+ IntSupplier nextHash = hashGenerator ;
535+ this .hash = nextHash != null ? nextHash .getAsInt () : generateKey ();
530536 }
531537
532538 /**
@@ -552,11 +558,11 @@ public T get() {
552558 // This code should perhaps be in class Cache. We do it
553559 // here because the generated code is small and fast and
554560 // we really want it to be inlined in the caller.
555- int n = (hash & Cache .SLOT_MASK ) * 2 ;
561+ int n = (hash & Cache .Constants . SLOT_MASK ) * 2 ;
556562 if (objects [n ] == this ) {
557563 return (T )objects [n + 1 ];
558564 }
559- n = ((hash >>> Cache .INDEX_BITS ) & Cache .SLOT_MASK ) * 2 ;
565+ n = ((hash >>> Cache .INDEX_BITS ) & Cache .Constants . SLOT_MASK ) * 2 ;
560566 if (objects [n ] == this ) {
561567 return (T )objects [n + 1 ];
562568 }
@@ -580,11 +586,11 @@ private T slowGet() {
580586 public boolean isBound () {
581587 Object [] objects = scopedValueCache ();
582588 if (objects != null ) {
583- int n = (hash & Cache .SLOT_MASK ) * 2 ;
589+ int n = (hash & Cache .Constants . SLOT_MASK ) * 2 ;
584590 if (objects [n ] == this ) {
585591 return true ;
586592 }
587- n = ((hash >>> Cache .INDEX_BITS ) & Cache .SLOT_MASK ) * 2 ;
593+ n = ((hash >>> Cache .INDEX_BITS ) & Cache .Constants . SLOT_MASK ) * 2 ;
588594 if (objects [n ] == this ) {
589595 return true ;
590596 }
@@ -688,17 +694,17 @@ private static Snapshot scopedValueBindings() {
688694
689695 private static int nextKey = 0xf0f0_f0f0 ;
690696
691- // A Marsaglia xor-shift generator used to generate hashes. This one has full period, so
692- // it generates 2**32 - 1 hashes before it repeats. We're going to use the lowest n bits
693- // and the next n bits as cache indexes, so we make sure that those indexes map
694- // to different slots in the cache.
697+ // A Marsaglia xor-shift generator used to generate hashes. This one has
698+ // full period, so it generates 2**32 - 1 hashes before it repeats. We're
699+ // going to use the lowest n bits and the next n bits as cache indexes, so
700+ // we make sure that those indexes map to different slots in the cache.
695701 private static synchronized int generateKey () {
696702 int x = nextKey ;
697703 do {
698704 x ^= x >>> 12 ;
699705 x ^= x << 9 ;
700706 x ^= x >>> 23 ;
701- } while (Cache .primarySlot (x ) == Cache .secondarySlot (x ));
707+ } while ((( Cache .primaryIndex (x ) ^ Cache .secondaryIndex (x )) & 1 ) == 0 );
702708 return (nextKey = x );
703709 }
704710
@@ -709,7 +715,7 @@ private static synchronized int generateKey() {
709715 * @return the bitmask
710716 */
711717 int bitmask () {
712- return (1 << Cache .primaryIndex (this )) | (1 << (Cache .secondaryIndex (this ) + Cache .TABLE_SIZE ));
718+ return (1 << Cache .primaryIndex (hash )) | (1 << (Cache .secondaryIndex (hash ) + Cache .TABLE_SIZE ));
713719 }
714720
715721 // Return true iff bitmask, considered as a set of bits, contains all
@@ -727,57 +733,100 @@ private static final class Cache {
727733 static final int TABLE_MASK = TABLE_SIZE - 1 ;
728734 static final int PRIMARY_MASK = (1 << TABLE_SIZE ) - 1 ;
729735
730- // The number of elements in the cache array, and a bit mask used to
731- // select elements from it.
732- private static final int CACHE_TABLE_SIZE , SLOT_MASK ;
733- // The largest cache we allow. Must be a power of 2 and greater than
734- // or equal to 2.
735- private static final int MAX_CACHE_SIZE = 16 ;
736-
737- static {
738- final String propertyName = "java.lang.ScopedValue.cacheSize" ;
739- var sizeString = System .getProperty (propertyName , "16" );
740- var cacheSize = Integer .valueOf (sizeString );
741- if (cacheSize < 2 || cacheSize > MAX_CACHE_SIZE ) {
742- cacheSize = MAX_CACHE_SIZE ;
743- System .err .println (propertyName + " is out of range: is " + sizeString );
744- }
745- if ((cacheSize & (cacheSize - 1 )) != 0 ) { // a power of 2
746- cacheSize = MAX_CACHE_SIZE ;
747- System .err .println (propertyName + " must be an integer power of 2: is " + sizeString );
736+
737+ // This class serves to defer initialization of some values until they
738+ // are needed. In particular, we must not invoke System.getProperty
739+ // early in the JDK boot process, because that leads to a circular class
740+ // initialization dependency.
741+ //
742+ // In more detail:
743+ //
744+ // The size of the cache depends on System.getProperty. Generating the
745+ // hash of an instance of ScopedValue depends on ThreadLocalRandom.
746+ //
747+ // Invoking either of these early in the JDK boot process will cause
748+ // startup to fail with an unrecoverable circular dependency.
749+ //
750+ // To break these cycles we allow scoped values to be created (but not
751+ // used) without invoking either System.getProperty or
752+ // ThreadLocalRandom. To do this we defer querying System.getProperty
753+ // until the first reference to CACHE_TABLE_SIZE, and we define a local
754+ // hash generator which is used until CACHE_TABLE_SIZE is initialized.
755+
756+ private static class Constants {
757+ // The number of elements in the cache array, and a bit mask used to
758+ // select elements from it.
759+ private static final int CACHE_TABLE_SIZE , SLOT_MASK ;
760+ // The largest cache we allow. Must be a power of 2 and greater than
761+ // or equal to 2.
762+ private static final int MAX_CACHE_SIZE = 16 ;
763+
764+ private static final JavaUtilConcurrentTLRAccess THREAD_LOCAL_RANDOM_ACCESS
765+ = SharedSecrets .getJavaUtilConcurrentTLRAccess ();
766+
767+ static {
768+ final String propertyName = "java.lang.ScopedValue.cacheSize" ;
769+ var sizeString = System .getProperty (propertyName , "16" );
770+ var cacheSize = Integer .valueOf (sizeString );
771+ if (cacheSize < 2 || cacheSize > MAX_CACHE_SIZE ) {
772+ cacheSize = MAX_CACHE_SIZE ;
773+ System .err .println (propertyName + " is out of range: is " + sizeString );
774+ }
775+ if ((cacheSize & (cacheSize - 1 )) != 0 ) { // a power of 2
776+ cacheSize = MAX_CACHE_SIZE ;
777+ System .err .println (propertyName + " must be an integer power of 2: is " + sizeString );
778+ }
779+ CACHE_TABLE_SIZE = cacheSize ;
780+ SLOT_MASK = cacheSize - 1 ;
781+
782+ // hashGenerator is set here (in class Constants rather than
783+ // in global scope) in order not to initialize
784+ // j.u.c.ThreadLocalRandom early in the JDK boot process.
785+ // After this static initialization, new instances of
786+ // ScopedValue will be initialized by a thread-local random
787+ // generator.
788+ hashGenerator = new IntSupplier () {
789+ @ Override
790+ public int getAsInt () {
791+ int x ;
792+ do {
793+ x = THREAD_LOCAL_RANDOM_ACCESS
794+ .nextSecondaryThreadLocalRandomSeed ();
795+ } while (Cache .primarySlot (x ) == Cache .secondarySlot (x ));
796+ return x ;
797+ }
798+ };
748799 }
749- CACHE_TABLE_SIZE = cacheSize ;
750- SLOT_MASK = cacheSize - 1 ;
751800 }
752801
753- static int primaryIndex (ScopedValue <?> key ) {
754- return key . hash & TABLE_MASK ;
802+ static int primaryIndex (int hash ) {
803+ return hash & Cache . TABLE_MASK ;
755804 }
756805
757- static int secondaryIndex (ScopedValue <?> key ) {
758- return (key . hash >> INDEX_BITS ) & TABLE_MASK ;
806+ static int secondaryIndex (int hash ) {
807+ return (hash >> INDEX_BITS ) & Cache . TABLE_MASK ;
759808 }
760809
761810 private static int primarySlot (ScopedValue <?> key ) {
762- return key .hashCode () & SLOT_MASK ;
811+ return key .hashCode () & Constants . SLOT_MASK ;
763812 }
764813
765814 private static int secondarySlot (ScopedValue <?> key ) {
766- return (key .hash >> INDEX_BITS ) & SLOT_MASK ;
815+ return (key .hash >> INDEX_BITS ) & Constants . SLOT_MASK ;
767816 }
768817
769818 static int primarySlot (int hash ) {
770- return hash & SLOT_MASK ;
819+ return hash & Constants . SLOT_MASK ;
771820 }
772821
773822 static int secondarySlot (int hash ) {
774- return (hash >> INDEX_BITS ) & SLOT_MASK ;
823+ return (hash >> INDEX_BITS ) & Constants . SLOT_MASK ;
775824 }
776825
777826 static void put (ScopedValue <?> key , Object value ) {
778827 Object [] theCache = scopedValueCache ();
779828 if (theCache == null ) {
780- theCache = new Object [CACHE_TABLE_SIZE * 2 ];
829+ theCache = new Object [Constants . CACHE_TABLE_SIZE * 2 ];
781830 setScopedValueCache (theCache );
782831 }
783832 // Update the cache to replace one entry with the value we just looked up.
@@ -813,26 +862,23 @@ private static void setKey(Object[] objs, int n, Object key) {
813862 objs [n * 2 ] = key ;
814863 }
815864
816- private static final JavaUtilConcurrentTLRAccess THREAD_LOCAL_RANDOM_ACCESS
817- = SharedSecrets .getJavaUtilConcurrentTLRAccess ();
818-
819865 // Return either true or false, at pseudo-random, with a bias towards true.
820866 // This chooses either the primary or secondary cache slot, but the
821867 // primary slot is approximately twice as likely to be chosen as the
822868 // secondary one.
823869 private static boolean chooseVictim () {
824- int r = THREAD_LOCAL_RANDOM_ACCESS .nextSecondaryThreadLocalRandomSeed ();
870+ int r = Constants . THREAD_LOCAL_RANDOM_ACCESS .nextSecondaryThreadLocalRandomSeed ();
825871 return (r & 15 ) >= 5 ;
826872 }
827873
828874 // Null a set of cache entries, indicated by the 1-bits given
829875 static void invalidate (int toClearBits ) {
830- toClearBits = (toClearBits >>> TABLE_SIZE ) | ( toClearBits & PRIMARY_MASK ) ;
876+ toClearBits = (( toClearBits >>> Cache . TABLE_SIZE ) | toClearBits ) & PRIMARY_MASK ;
831877 Object [] objects ;
832878 if ((objects = scopedValueCache ()) != null ) {
833879 for (int bits = toClearBits ; bits != 0 ; ) {
834880 int index = Integer .numberOfTrailingZeros (bits );
835- setKeyAndObjectAt (objects , index & SLOT_MASK , null , null );
881+ setKeyAndObjectAt (objects , index & Constants . SLOT_MASK , null , null );
836882 bits &= ~1 << index ;
837883 }
838884 }
0 commit comments