4848import static org .objectweb .asm .Opcodes .ACC_VARARGS ;
4949
5050import java .io .IOException ;
51+ import java .lang .ref .WeakReference ;
5152import java .lang .reflect .Constructor ;
5253import java .lang .reflect .InvocationTargetException ;
5354import java .lang .reflect .Method ;
5455import java .lang .reflect .Modifier ;
5556import java .util .*;
57+ import java .util .concurrent .atomic .AtomicReferenceFieldUpdater ;
5658import java .util .stream .Collectors ;
5759
5860import org .jruby .anno .JRubyClass ;
9395import org .jruby .util .Loader ;
9496import org .jruby .util .OneShotClassLoader ;
9597import org .jruby .util .StringSupport ;
96- import org .jruby .util .collections .ConcurrentWeakHashMap ;
9798import org .jruby .util .log .Logger ;
9899import org .jruby .util .log .LoggerFactory ;
99100import org .objectweb .asm .AnnotationVisitor ;
110111public class RubyClass extends RubyModule {
111112
112113 private static final Logger LOG = LoggerFactory .getLogger (RubyClass .class );
114+ private static final double SUBCLASSES_CLEAN_FACTOR = 0.25 ;
113115
114116 public static void createClassClass (Ruby runtime , RubyClass classClass ) {
115117 classClass .setClassIndex (ClassIndex .CLASS );
@@ -1008,20 +1010,11 @@ public IRubyObject initialize_copy(IRubyObject original) {
10081010 return this ;
10091011 }
10101012
1011- protected void setModuleSuperClass (RubyClass superClass ) {
1012- // remove us from old superclass's child classes
1013- if (this .superClass != null ) this .superClass .removeSubclass (this );
1014- // add us to new superclass's child classes
1015- superClass .addSubclass (this );
1016- // update superclass reference
1017- setSuperClass (superClass );
1018- }
1019-
10201013 @ JRubyMethod
10211014 public IRubyObject subclasses (ThreadContext context ) {
1022- RubyArray <RubyClass > subs = RubyArray . newArray (context . runtime );
1023-
1024- concreteSubclasses (subs );
1015+ RubyArray <RubyClass > subs = newConcreteSubclassesArray (context );
1016+ int clearedCount = concreteSubclasses ( subs );
1017+ finishConcreteSubclasses (subs , clearedCount );
10251018
10261019 return subs ;
10271020 }
@@ -1058,44 +1051,127 @@ public final Collection<RubyClass> subclasses() {
10581051 }
10591052
10601053 public Collection <RubyClass > subclasses (boolean includeDescendants ) {
1061- Map <RubyClass , Object > subclasses = this .subclasses ;
1062- if (subclasses != null ) {
1063- Collection <RubyClass > mine = new ArrayList <>();
1064- subclassesInner (mine , includeDescendants );
1054+ Collection <RubyClass > mine = newSubclassesList (includeDescendants );
1055+ int clearedCount = subclassesInner (mine , includeDescendants );
1056+ finishSubclasses (mine , clearedCount , includeDescendants );
1057+
1058+ return mine ;
1059+ }
1060+
1061+ private RubyArray <RubyClass > newConcreteSubclassesArray (ThreadContext context ) {
1062+ RubyArray <RubyClass > subs = RubyArray .newArray (context .runtime , this .concreteSubclassesEstimate );
1063+ return subs ;
1064+ }
1065+
1066+ private Collection <RubyClass > newSubclassesList (boolean includeDescendants ) {
1067+ Collection <RubyClass > mine = new ArrayList <>(includeDescendants ? allDescendantsEstimate : allSubclassesEstimate );
1068+ return mine ;
1069+ }
1070+
1071+ private int subclassesInner (Collection <RubyClass > mine , boolean includeDescendants ) {
1072+ SubclassNode subclassNode = this .subclassNode ;
1073+ int clearedCount = 0 ;
1074+ while (subclassNode != null ) {
1075+ RubyClass klass = subclassNode .ref .get ();
1076+ subclassNode = subclassNode .next ;
10651077
1066- return mine ;
1078+ if (klass == null ) {
1079+ clearedCount ++;
1080+ continue ;
1081+ }
1082+
1083+ processSubclass (mine , includeDescendants , klass );
10671084 }
1068- return Collections . EMPTY_LIST ;
1085+ return clearedCount ;
10691086 }
10701087
1071- private void subclassesInner (Collection <RubyClass > mine , boolean includeDescendants ) {
1072- Map <RubyClass , Object > subclasses = this .subclasses ;
1073- if (subclasses != null ) {
1074- Set <RubyClass > keys = subclasses .keySet ();
1075- mine .addAll (keys );
1076- if (includeDescendants ) {
1077- for (RubyClass klass : keys ) {
1078- klass .subclassesInner (mine , includeDescendants );
1079- }
1088+ private static void processSubclass (Collection <RubyClass > mine , boolean includeDescendants , RubyClass klass ) {
1089+ mine .add (klass );
1090+
1091+ if (includeDescendants ) klass .subclassesInner (mine , includeDescendants );
1092+ }
1093+
1094+ private void finishSubclasses (Collection <RubyClass > mine , int clearedCount , boolean includeDescendants ) {
1095+ int newSize = mine .size ();
1096+ if (includeDescendants ) {
1097+ allDescendantsEstimate = newSize ;
1098+ } else {
1099+ allSubclassesEstimate = newSize ;
1100+ }
1101+ cleanSubclasses (newSize , clearedCount );
1102+ }
1103+
1104+ private int concreteSubclasses (RubyArray <RubyClass > subs ) {
1105+ SubclassNode subclassNode = this .subclassNode ;
1106+ int clearedCount = 0 ;
1107+ while (subclassNode != null ) {
1108+ RubyClass klass = subclassNode .ref .get ();
1109+ subclassNode = subclassNode .next ;
1110+
1111+ if (klass == null ) {
1112+ clearedCount ++;
1113+ continue ;
10801114 }
1115+
1116+ processConcreteSubclass (subs , klass );
10811117 }
1118+ return clearedCount ;
10821119 }
10831120
1084- private void concreteSubclasses (Collection <RubyClass > subs ) {
1085- Map <RubyClass , Object > subclasses = this .subclasses ;
1086- if (subclasses != null ) {
1087- Set <RubyClass > keys = subclasses .keySet ();
1088- for (RubyClass klass : keys ) {
1089- if (klass .isSingleton ()) continue ;
1090- if (klass .isIncluded () || klass .isPrepended ()) {
1091- klass .concreteSubclasses (subs );
1092- continue ;
1093- }
1094- subs .add (klass );
1121+ private static void processConcreteSubclass (RubyArray <RubyClass > subs , RubyClass klass ) {
1122+ if (!klass .isSingleton ()) {
1123+ if (klass .isIncluded () || klass .isPrepended ()) {
1124+ klass .concreteSubclasses (subs );
1125+ } else {
1126+ subs .append (klass );
10951127 }
10961128 }
10971129 }
10981130
1131+ private void finishConcreteSubclasses (RubyArray <RubyClass > subs , int clearedCount ) {
1132+ int newSize = subs .size ();
1133+ concreteSubclassesEstimate = newSize + 4 ;
1134+ cleanSubclasses (newSize , clearedCount );
1135+ }
1136+
1137+ private void cleanSubclasses (int size , int vacated ) {
1138+ // tidy up if more than 25% cleared references
1139+ if ((double ) vacated / size > SUBCLASSES_CLEAN_FACTOR ) {
1140+ SubclassNode subclassNode = this .subclassNode ;
1141+ SubclassNode newTop = rebuildSubclasses (subclassNode );
1142+ while (!SUBCLASS_UPDATER .compareAndSet (this , subclassNode , newTop )) {
1143+ subclassNode = this .subclassNode ;
1144+ newTop = rebuildSubclasses (subclassNode );
1145+ }
1146+ }
1147+ }
1148+
1149+ private static SubclassNode rebuildSubclasses (SubclassNode subclassNode ) {
1150+ SubclassNode newTop = null ;
1151+ while (subclassNode != null ) {
1152+ WeakReference <RubyClass > ref = subclassNode .ref ;
1153+ RubyClass klass = ref .get ();
1154+ subclassNode = subclassNode .next ;
1155+ if (klass == null ) continue ;
1156+ newTop = new SubclassNode (ref , newTop );
1157+ }
1158+ return newTop ;
1159+ }
1160+
1161+ // TODO: make into a Record
1162+ static class SubclassNode {
1163+ final SubclassNode next ;
1164+ final WeakReference <RubyClass > ref ;
1165+ SubclassNode (RubyClass klass , SubclassNode next ) {
1166+ ref = new WeakReference <>(klass );
1167+ this .next = next ;
1168+ }
1169+ SubclassNode (WeakReference <RubyClass > ref , SubclassNode next ) {
1170+ this .ref = ref ;
1171+ this .next = next ;
1172+ }
1173+ }
1174+
10991175 /**
11001176 * Add a new subclass to the weak set of subclasses.
11011177 *
@@ -1106,18 +1182,12 @@ private void concreteSubclasses(Collection<RubyClass> subs) {
11061182 * @param subclass The subclass to add
11071183 */
11081184 public void addSubclass (RubyClass subclass ) {
1109- Map <RubyClass , Object > subclasses = this .subclasses ;
1110- if (subclasses == null ) {
1111- // check again
1112- synchronized (this ) {
1113- subclasses = this .subclasses ;
1114- if (subclasses == null ) {
1115- this .subclasses = subclasses = new ConcurrentWeakHashMap <>(4 , 0.75f , 1 );
1116- }
1117- }
1185+ SubclassNode subclassNode = this .subclassNode ;
1186+ SubclassNode newNode = new SubclassNode (subclass , subclassNode );
1187+ while (!SUBCLASS_UPDATER .compareAndSet (this , subclassNode , newNode )) {
1188+ subclassNode = this .subclassNode ;
1189+ newNode = new SubclassNode (subclass , subclassNode );
11181190 }
1119-
1120- subclasses .put (subclass , NEVER );
11211191 }
11221192
11231193 /**
@@ -1126,10 +1196,16 @@ public void addSubclass(RubyClass subclass) {
11261196 * @param subclass The subclass to remove
11271197 */
11281198 public void removeSubclass (RubyClass subclass ) {
1129- Map <RubyClass , Object > subclasses = this .subclasses ;
1130- if (subclasses == null ) return ;
1131-
1132- subclasses .remove (subclass );
1199+ SubclassNode subclassNode = this .subclassNode ;
1200+ while (subclassNode != null ) {
1201+ WeakReference <RubyClass > ref = subclassNode .ref ;
1202+ RubyClass klass = ref .get ();
1203+ if (klass == subclass ) {
1204+ ref .clear ();
1205+ return ;
1206+ }
1207+ subclassNode = subclassNode .next ;
1208+ }
11331209 }
11341210
11351211 /**
@@ -1139,20 +1215,25 @@ public void removeSubclass(RubyClass subclass) {
11391215 * @param newSubclass The subclass to replace it with
11401216 */
11411217 public void replaceSubclass (RubyClass subclass , RubyClass newSubclass ) {
1142- Map <RubyClass , Object > subclasses = this .subclasses ;
1143- if (subclasses == null ) return ;
1144-
1145- subclasses .remove (subclass );
1146- subclasses .put (newSubclass , NEVER );
1218+ removeSubclass (subclass );
1219+ addSubclass (newSubclass );
11471220 }
11481221
1222+ /**
1223+ * make this class and all subclasses sync
1224+ */
11491225 @ Override
11501226 public void becomeSynchronized () {
1151- // make this class and all subclasses sync
11521227 super .becomeSynchronized ();
1153- Map <RubyClass , Object > subclasses = this .subclasses ;
1154- if (subclasses != null ) {
1155- for (RubyClass subclass : subclasses .keySet ()) subclass .becomeSynchronized ();
1228+
1229+ SubclassNode subclassNode = this .subclassNode ;
1230+ while (subclassNode != null ) {
1231+ WeakReference <RubyClass > ref = subclassNode .ref ;
1232+ RubyClass klass = ref .get ();
1233+ if (klass != null ) {
1234+ klass .becomeSynchronized ();
1235+ }
1236+ subclassNode = subclassNode .next ;
11561237 }
11571238 }
11581239
@@ -1172,9 +1253,14 @@ public void becomeSynchronized() {
11721253 public void invalidateCacheDescendants () {
11731254 super .invalidateCacheDescendants ();
11741255
1175- Map <RubyClass , Object > subclasses = this .subclasses ;
1176- if (subclasses != null ) {
1177- for (RubyClass subclass : subclasses .keySet ()) subclass .invalidateCacheDescendants ();
1256+ SubclassNode subclassNode = this .subclassNode ;
1257+ while (subclassNode != null ) {
1258+ WeakReference <RubyClass > ref = subclassNode .ref ;
1259+ RubyClass klass = ref .get ();
1260+ if (klass != null ) {
1261+ klass .invalidateCacheDescendants ();
1262+ }
1263+ subclassNode = subclassNode .next ;
11781264 }
11791265 }
11801266
@@ -1185,12 +1271,15 @@ public void addInvalidatorsAndFlush(List<Invalidator> invalidators) {
11851271 // if we're not at boot time, don't bother fully clearing caches
11861272 if (!runtime .isBootingCore ()) cachedMethods .clear ();
11871273
1188- Map <RubyClass , Object > subclasses = this .subclasses ;
1189- // no subclasses, don't bother with lock and iteration
1190- if (subclasses == null || subclasses .isEmpty ()) return ;
1191-
1192- // cascade into subclasses
1193- for (RubyClass subclass : subclasses .keySet ()) subclass .addInvalidatorsAndFlush (invalidators );
1274+ SubclassNode subclassNode = this .subclassNode ;
1275+ while (subclassNode != null ) {
1276+ WeakReference <RubyClass > ref = subclassNode .ref ;
1277+ RubyClass klass = ref .get ();
1278+ if (klass != null ) {
1279+ klass .addInvalidatorsAndFlush (invalidators );
1280+ }
1281+ subclassNode = subclassNode .next ;
1282+ }
11941283 }
11951284
11961285 public final Ruby getClassRuntime () {
@@ -3085,7 +3174,11 @@ public IRubyObject invokeFrom(ThreadContext context, CallType callType, IRubyObj
30853174 protected final Ruby runtime ;
30863175 private ObjectAllocator allocator ; // the default allocator
30873176 protected ObjectMarshal marshal ;
3088- private volatile Map <RubyClass , Object > subclasses ;
3177+ private volatile SubclassNode subclassNode ;
3178+ private static final AtomicReferenceFieldUpdater SUBCLASS_UPDATER = AtomicReferenceFieldUpdater .newUpdater (RubyClass .class , SubclassNode .class , "subclassNode" );
3179+ private int concreteSubclassesEstimate = 4 ;
3180+ private int allDescendantsEstimate = 4 ;
3181+ private int allSubclassesEstimate = 4 ;
30893182 public static final int CS_IDX_INITIALIZE = 0 ;
30903183 public enum CS_NAMES {
30913184 INITIALIZE ("initialize" );
0 commit comments