8190427: Test for JDK-8165198 fails intermittently because of GC
authorhannesw
Wed, 08 Nov 2017 12:15:24 +0100
changeset 47713 530f16bacbfd
parent 47712 bde0215f1f70
child 47714 28e37caf50c1
child 55782 0586f8664ba8
8190427: Test for JDK-8165198 fails intermittently because of GC Reviewed-by: jlaskey, sundar
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeDebug.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyListeners.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertySwitchPoints.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SharedPropertyMap.java
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeDebug.java	Tue Nov 07 16:19:55 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeDebug.java	Wed Nov 08 12:15:24 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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 @@
 import jdk.nashorn.internal.objects.annotations.Where;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.JSType;
-import jdk.nashorn.internal.runtime.PropertyListeners;
+import jdk.nashorn.internal.runtime.PropertySwitchPoints;
 import jdk.nashorn.internal.runtime.PropertyMap;
 import jdk.nashorn.internal.runtime.Scope;
 import jdk.nashorn.internal.runtime.ScriptFunction;
@@ -244,7 +244,7 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
     public static int getListenerCount(final Object self, final Object obj) {
-        return (obj instanceof ScriptObject) ? PropertyListeners.getListenerCount((ScriptObject) obj) : 0;
+        return (obj instanceof ScriptObject) ? PropertySwitchPoints.getSwitchPointCount((ScriptObject) obj) : 0;
     }
 
     /**
@@ -260,8 +260,8 @@
 
         out.println("ScriptObject count " + ScriptObject.getCount());
         out.println("Scope count " + Scope.getScopeCount());
-        out.println("ScriptObject listeners added " + PropertyListeners.getListenersAdded());
-        out.println("ScriptObject listeners removed " + PropertyListeners.getListenersRemoved());
+        out.println("Property SwitchPoints added " + PropertySwitchPoints.getSwitchPointsAdded());
+        out.println("Property SwitchPoints invalidated " + PropertySwitchPoints.getSwitchPointsInvalidated());
         out.println("ScriptFunction constructor calls " + ScriptFunction.getConstructorCount());
         out.println("ScriptFunction invokes " + ScriptFunction.getInvokes());
         out.println("ScriptFunction allocations " + ScriptFunction.getAllocations());
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyListeners.java	Tue Nov 07 16:19:55 2017 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,260 +0,0 @@
-/*
- * Copyright (c) 2010, 2014, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.nashorn.internal.runtime;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.concurrent.atomic.LongAdder;
-
-/**
- * Helper class to manage property listeners and notification.
- */
-public class PropertyListeners {
-
-    private Map<Object, WeakPropertyMapSet> listeners;
-
-    // These counters are updated in debug mode
-    private static LongAdder listenersAdded;
-    private static LongAdder listenersRemoved;
-
-    static {
-        if (Context.DEBUG) {
-            listenersAdded = new LongAdder();
-            listenersRemoved = new LongAdder();
-        }
-    }
-
-    /**
-     * Copy constructor
-     * @param listener listener to copy
-     */
-    PropertyListeners(final PropertyListeners listener) {
-        if (listener != null && listener.listeners != null) {
-            this.listeners = new WeakHashMap<>();
-            // We need to copy the nested weak sets in order to avoid concurrent modification issues, see JDK-8146274
-            synchronized (listener) {
-                for (final Map.Entry<Object, WeakPropertyMapSet> entry : listener.listeners.entrySet()) {
-                    this.listeners.put(entry.getKey(), new WeakPropertyMapSet(entry.getValue()));
-                }
-            }
-        }
-    }
-
-    /**
-     * Return aggregate listeners added to all PropertyListenerManagers
-     * @return the listenersAdded
-     */
-    public static long getListenersAdded() {
-        return listenersAdded.longValue();
-    }
-
-    /**
-     * Return aggregate listeners removed from all PropertyListenerManagers
-     * @return the listenersRemoved
-     */
-    public static long getListenersRemoved() {
-        return listenersRemoved.longValue();
-    }
-
-    /**
-     * Return number of listeners added to a ScriptObject.
-     * @param obj the object
-     * @return the listener count
-     */
-    public static int getListenerCount(final ScriptObject obj) {
-        return obj.getMap().getListenerCount();
-    }
-
-    /**
-     * Return the number of listeners added to this PropertyListeners instance.
-     * @return the listener count;
-     */
-    public int getListenerCount() {
-        return listeners == null ? 0 : listeners.size();
-    }
-
-    // Property listener management methods
-
-    /**
-     * Add {@code propertyMap} as property listener to {@code listeners} using key {@code key} by
-     * creating and returning a new {@code PropertyListeners} instance.
-     *
-     * @param listeners the original property listeners instance, may be null
-     * @param key the property key
-     * @param propertyMap the property map
-     * @return the new property map
-     */
-    public static PropertyListeners addListener(final PropertyListeners listeners, final String key, final PropertyMap propertyMap) {
-        final PropertyListeners newListeners;
-        if (listeners == null || !listeners.containsListener(key, propertyMap)) {
-            newListeners = new PropertyListeners(listeners);
-            newListeners.addListener(key, propertyMap);
-            return newListeners;
-        }
-        return listeners;
-    }
-
-    /**
-     * Checks whether {@code propertyMap} is registered as listener with {@code key}.
-     *
-     * @param key the property key
-     * @param propertyMap the property map
-     * @return true if property map is registered with property key
-     */
-    synchronized boolean containsListener(final String key, final PropertyMap propertyMap) {
-        if (listeners == null) {
-            return false;
-        }
-        final WeakPropertyMapSet set = listeners.get(key);
-        return set != null && set.contains(propertyMap);
-    }
-
-    /**
-     * Add a property listener to this object.
-     *
-     * @param propertyMap The property listener that is added.
-     */
-    synchronized final void addListener(final String key, final PropertyMap propertyMap) {
-        if (Context.DEBUG) {
-            listenersAdded.increment();
-        }
-        if (listeners == null) {
-            listeners = new WeakHashMap<>();
-        }
-
-        WeakPropertyMapSet set = listeners.get(key);
-        if (set == null) {
-            set = new WeakPropertyMapSet();
-            listeners.put(key, set);
-        }
-        if (!set.contains(propertyMap)) {
-            set.add(propertyMap);
-        }
-    }
-
-    /**
-     * A new property is being added.
-     *
-     * @param prop The new Property added.
-     */
-    public synchronized void propertyAdded(final Property prop) {
-        if (listeners != null) {
-            final WeakPropertyMapSet set = listeners.get(prop.getKey());
-            if (set != null) {
-                for (final PropertyMap propertyMap : set.elements()) {
-                    propertyMap.propertyAdded(prop, false);
-                }
-                listeners.remove(prop.getKey());
-                if (Context.DEBUG) {
-                    listenersRemoved.increment();
-                }
-            }
-        }
-    }
-
-    /**
-     * An existing property is being deleted.
-     *
-     * @param prop The property being deleted.
-     */
-    public synchronized void propertyDeleted(final Property prop) {
-        if (listeners != null) {
-            final WeakPropertyMapSet set = listeners.get(prop.getKey());
-            if (set != null) {
-                for (final PropertyMap propertyMap : set.elements()) {
-                    propertyMap.propertyDeleted(prop, false);
-                }
-                listeners.remove(prop.getKey());
-                if (Context.DEBUG) {
-                    listenersRemoved.increment();
-                }
-            }
-        }
-    }
-
-    /**
-     * An existing Property is being replaced with a new Property.
-     *
-     * @param oldProp The old property that is being replaced.
-     * @param newProp The new property that replaces the old property.
-     *
-     */
-    public synchronized void propertyModified(final Property oldProp, final Property newProp) {
-        if (listeners != null) {
-            final WeakPropertyMapSet set = listeners.get(oldProp.getKey());
-            if (set != null) {
-                for (final PropertyMap propertyMap : set.elements()) {
-                    propertyMap.propertyModified(oldProp, newProp, false);
-                }
-                listeners.remove(oldProp.getKey());
-                if (Context.DEBUG) {
-                    listenersRemoved.increment();
-                }
-            }
-        }
-    }
-
-    /**
-     * Callback for when a proto is changed
-     */
-    public synchronized void protoChanged() {
-        if (listeners != null) {
-            for (final WeakPropertyMapSet set : listeners.values()) {
-                for (final PropertyMap propertyMap : set.elements()) {
-                    propertyMap.protoChanged(false);
-                }
-            }
-            listeners.clear();
-        }
-    }
-
-    private static class WeakPropertyMapSet {
-
-        private final WeakHashMap<PropertyMap, Boolean> map;
-
-        WeakPropertyMapSet() {
-            this.map = new WeakHashMap<>();
-        }
-
-        WeakPropertyMapSet(final WeakPropertyMapSet set) {
-            this.map = new WeakHashMap<>(set.map);
-        }
-
-        void add(final PropertyMap propertyMap) {
-            map.put(propertyMap, Boolean.TRUE);
-        }
-
-        boolean contains(final PropertyMap propertyMap) {
-            return map.containsKey(propertyMap);
-        }
-
-        Set<PropertyMap> elements() {
-            return map.keySet();
-        }
-
-    }
-}
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java	Tue Nov 07 16:19:55 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java	Wed Nov 08 12:15:24 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -40,9 +40,9 @@
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.atomic.LongAdder;
 import jdk.nashorn.internal.runtime.options.Options;
@@ -95,17 +95,14 @@
      * property map should only be used if it the same as the actual prototype map. */
     private transient SharedPropertyMap sharedProtoMap;
 
-    /** {@link SwitchPoint}s for gets on inherited properties. */
-    private transient HashMap<Object, SwitchPoint> protoSwitches;
-
     /** History of maps, used to limit map duplication. */
     private transient WeakHashMap<Property, Reference<PropertyMap>> history;
 
     /** History of prototypes, used to limit map duplication. */
     private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
 
-    /** property listeners */
-    private transient PropertyListeners listeners;
+    /** SwitchPoints for properties inherited form this map */
+    private transient PropertySwitchPoints propertySwitchPoints;
 
     private transient BitSet freeSlots;
 
@@ -147,8 +144,8 @@
         this.fieldCount   = fieldCount;
         this.fieldMaximum = propertyMap.fieldMaximum;
         this.className    = propertyMap.className;
-        // We inherit the parent property listeners instance. It will be cloned when a new listener is added.
-        this.listeners    = propertyMap.listeners;
+        // We inherit the parent property propertySwitchPoints instance. It will be cloned when a new listener is added.
+        this.propertySwitchPoints = propertyMap.propertySwitchPoints;
         this.freeSlots    = propertyMap.freeSlots;
         this.sharedProtoMap = propertyMap.sharedProtoMap;
         this.softReferenceDerivationLimit = softReferenceDerivationLimit;
@@ -245,142 +242,70 @@
     }
 
     /**
-     * Get the number of listeners of this map
+     * Get the number of property SwitchPoints of this map
      *
-     * @return the number of listeners
+     * @return the number of property SwitchPoints
      */
-    public int getListenerCount() {
-        return listeners == null ? 0 : listeners.getListenerCount();
-    }
-
-    /**
-     * Add {@code listenerMap} as a listener to this property map for the given {@code key}.
-     *
-     * @param key the property name
-     * @param listenerMap the listener map
-     */
-    public void addListener(final String key, final PropertyMap listenerMap) {
-        if (listenerMap != this) {
-            // We need to clone listener instance when adding a new listener since we share
-            // the listeners instance with our parent maps that don't need to see the new listener.
-            listeners = PropertyListeners.addListener(listeners, key, listenerMap);
-        }
+    public int getSwitchPointCount() {
+        return propertySwitchPoints == null ? 0 : propertySwitchPoints.getSwitchPointCount();
     }
 
     /**
-     * A new property is being added.
+     * Add a property switchpoint to this property map for the given {@code key}.
      *
-     * @param property The new Property added.
-     * @param isSelf was the property added to this map?
+     * @param key the property name
+     * @param switchPoint the switchpoint
      */
-    public void propertyAdded(final Property property, final boolean isSelf) {
-        if (!isSelf) {
-            invalidateProtoSwitchPoint(property.getKey());
-        }
-        if (listeners != null) {
-            listeners.propertyAdded(property);
-        }
+    public void addSwitchPoint(final String key, final SwitchPoint switchPoint) {
+        // We need to clone listener instance when adding a new listener since we share
+        // the propertySwitchPoints instance with our parent maps that don't need to see the new listener.
+        propertySwitchPoints = PropertySwitchPoints.addSwitchPoint(propertySwitchPoints, key, switchPoint);
     }
 
     /**
-     * An existing property is being deleted.
+     * Method called when a property of an object using this property map is being created,
+     * modified, or deleted. If a switchpoint for the property exists it will be invalidated.
      *
-     * @param property The property being deleted.
-     * @param isSelf was the property deleted from this map?
+     * @param property The changed property.
      */
-    public void propertyDeleted(final Property property, final boolean isSelf) {
-        if (!isSelf) {
-            invalidateProtoSwitchPoint(property.getKey());
-        }
-        if (listeners != null) {
-            listeners.propertyDeleted(property);
+    public void propertyChanged(final Property property) {
+        if (propertySwitchPoints != null) {
+            propertySwitchPoints.invalidateProperty(property);
         }
     }
 
     /**
-     * An existing property is being redefined.
-     *
-     * @param oldProperty The old property
-     * @param newProperty The new property
-     * @param isSelf was the property modified on this map?
+     * Method called when the prototype of an object using this property map is changed.
      */
-    public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) {
-        if (!isSelf) {
-            invalidateProtoSwitchPoint(oldProperty.getKey());
-        }
-        if (listeners != null) {
-            listeners.propertyModified(oldProperty, newProperty);
-        }
-    }
-
-    /**
-     * The prototype of an object associated with this {@link PropertyMap} is changed.
-     *
-     * @param isSelf was the prototype changed on the object using this map?
-     */
-    public void protoChanged(final boolean isSelf) {
-        if (!isSelf) {
-            invalidateAllProtoSwitchPoints();
-        } else if (sharedProtoMap != null) {
+    void protoChanged() {
+        if (sharedProtoMap != null) {
             sharedProtoMap.invalidateSwitchPoint();
         }
-        if (listeners != null) {
-            listeners.protoChanged();
+        if (propertySwitchPoints != null) {
+            propertySwitchPoints.invalidateInheritedProperties(this);
         }
     }
 
     /**
-     * Return a SwitchPoint used to track changes of a property in a prototype.
+     * Returns a SwitchPoint for use with a property inherited from this or a parent map.
+     * If such a switchpoint exists, it will be invalidated when the property is modified
+     * in an object using this map. This method returns {@code null} if no swichpoint exists
+     * for the property.
      *
      * @param key Property key.
-     * @return A shared {@link SwitchPoint} for the property.
+     * @return A {@link SwitchPoint} for the property, or null.
      */
     public synchronized SwitchPoint getSwitchPoint(final String key) {
-        if (protoSwitches == null) {
-            protoSwitches = new HashMap<>();
-        }
-
-        SwitchPoint switchPoint = protoSwitches.get(key);
-        if (switchPoint == null) {
-            switchPoint = new SwitchPoint();
-            protoSwitches.put(key, switchPoint);
+        if (propertySwitchPoints != null) {
+            final Set<SwitchPoint> existingSwitchPoints = propertySwitchPoints.getSwitchPoints(key);
+            for (final SwitchPoint switchPoint : existingSwitchPoints) {
+                if (switchPoint != null && !switchPoint.hasBeenInvalidated()) {
+                    return switchPoint;
+                }
+            }
         }
 
-        return switchPoint;
-    }
-
-    /**
-     * Indicate that a prototype property has changed.
-     *
-     * @param key {@link Property} key to invalidate.
-     */
-    synchronized void invalidateProtoSwitchPoint(final Object key) {
-        if (protoSwitches != null) {
-            final SwitchPoint sp = protoSwitches.get(key);
-            if (sp != null) {
-                protoSwitches.remove(key);
-                if (Context.DEBUG) {
-                    protoInvalidations.increment();
-                }
-                SwitchPoint.invalidateAll(new SwitchPoint[]{sp});
-            }
-        }
-    }
-
-    /**
-     * Indicate that proto itself has changed in hierarchy somewhere.
-     */
-    synchronized void invalidateAllProtoSwitchPoints() {
-        if (protoSwitches != null) {
-            final int size = protoSwitches.size();
-            if (size > 0) {
-                if (Context.DEBUG) {
-                    protoInvalidations.add(size);
-                }
-                SwitchPoint.invalidateAll(protoSwitches.values().toArray(new SwitchPoint[0]));
-                protoSwitches.clear();
-            }
-        }
+        return null;
     }
 
     /**
@@ -452,7 +377,7 @@
      * @return New {@link PropertyMap} with {@link Property} added.
      */
     public final PropertyMap addPropertyNoHistory(final Property property) {
-        propertyAdded(property, true);
+        propertyChanged(property);
         return addPropertyInternal(property);
     }
 
@@ -464,7 +389,7 @@
      * @return New {@link PropertyMap} with {@link Property} added.
      */
     public final synchronized PropertyMap addProperty(final Property property) {
-        propertyAdded(property, true);
+        propertyChanged(property);
         PropertyMap newMap = checkHistory(property);
 
         if (newMap == null) {
@@ -494,7 +419,7 @@
      * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found.
      */
     public final synchronized PropertyMap deleteProperty(final Property property) {
-        propertyDeleted(property, true);
+        propertyChanged(property);
         PropertyMap newMap = checkHistory(property);
         final Object key = property.getKey();
 
@@ -529,7 +454,7 @@
      * @return New {@link PropertyMap} with {@link Property} replaced.
      */
     public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
-        propertyModified(oldProperty, newProperty, true);
+        propertyChanged(oldProperty);
         /*
          * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods.
          *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertySwitchPoints.java	Wed Nov 08 12:15:24 2017 +0100
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2010, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.runtime;
+
+import java.lang.invoke.SwitchPoint;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.LongAdder;
+
+/**
+ * Helper class for tracking and invalidation of switchpoints for inherited properties.
+ */
+public class PropertySwitchPoints {
+
+    private final Map<Object, WeakSwitchPointSet> switchPointMap = new HashMap<>();
+
+    private final static SwitchPoint[] EMPTY_SWITCHPOINT_ARRAY = new SwitchPoint[0];
+
+    // These counters are updated in debug mode
+    private static LongAdder switchPointsAdded;
+    private static LongAdder switchPointsInvalidated;
+
+    static {
+        if (Context.DEBUG) {
+            switchPointsAdded = new LongAdder();
+            switchPointsInvalidated = new LongAdder();
+        }
+    }
+
+    /**
+     * Copy constructor
+     *
+     * @param switchPoints Proto switchpoints to copy
+     */
+    private PropertySwitchPoints(final PropertySwitchPoints switchPoints) {
+        if (switchPoints != null) {
+            // We need to copy the nested weak sets in order to avoid concurrent modification issues, see JDK-8146274
+            synchronized (switchPoints) {
+                for (final Map.Entry<Object, WeakSwitchPointSet> entry : switchPoints.switchPointMap.entrySet()) {
+                    this.switchPointMap.put(entry.getKey(), new WeakSwitchPointSet(entry.getValue()));
+                }
+            }
+        }
+    }
+
+    /**
+     * Return aggregate switchpoints added to all ProtoSwitchPoints
+     * @return the number of switchpoints added
+     */
+    public static long getSwitchPointsAdded() {
+        return switchPointsAdded.longValue();
+    }
+
+    /**
+     * Return aggregate switchPointMap invalidated in all ProtoSwitchPoints
+     * @return the number of switchpoints invalidated
+     */
+    public static long getSwitchPointsInvalidated() {
+        return switchPointsInvalidated.longValue();
+    }
+
+    /**
+     * Return number of property switchPoints added to a ScriptObject.
+     * @param obj the object
+     * @return the switchpoint count
+     */
+    public static int getSwitchPointCount(final ScriptObject obj) {
+        return obj.getMap().getSwitchPointCount();
+    }
+
+    /**
+     * Return the number of switchpoints added to this ProtoSwitchPoints instance.
+     * @return the switchpoint count;
+     */
+    int getSwitchPointCount() {
+        return switchPointMap.size();
+    }
+
+    /**
+     * Add {@code switchPoint} to the switchpoints for for property {@code key}, creating
+     * and returning a new {@code ProtoSwitchPoints} instance if the switchpoint was not already contained
+     *
+     * @param oldSwitchPoints the original PropertySwitchPoints instance. May be null
+     * @param key the property key
+     * @param switchPoint the switchpoint to be added
+     * @return the new PropertySwitchPoints instance, or this instance if switchpoint was already contained
+     */
+    static PropertySwitchPoints addSwitchPoint(final PropertySwitchPoints oldSwitchPoints, final String key, final SwitchPoint switchPoint) {
+        if (oldSwitchPoints == null || !oldSwitchPoints.contains(key, switchPoint)) {
+            final PropertySwitchPoints newSwitchPoints = new PropertySwitchPoints(oldSwitchPoints);
+            newSwitchPoints.add(key, switchPoint);
+            return newSwitchPoints;
+        }
+        return oldSwitchPoints;
+    }
+
+    /**
+     * Checks whether {@code switchPoint} is contained in {@code key}'s set.
+     *
+     * @param key the property key
+     * @param switchPoint the switchPoint
+     * @return true if switchpoint is already contained for key
+     */
+    private synchronized boolean contains(final String key, final SwitchPoint switchPoint) {
+        final WeakSwitchPointSet set = this.switchPointMap.get(key);
+        return set != null && set.contains(switchPoint);
+    }
+
+    private synchronized void add(final String key, final SwitchPoint switchPoint) {
+        if (Context.DEBUG) {
+            switchPointsAdded.increment();
+        }
+
+        WeakSwitchPointSet set = this.switchPointMap.get(key);
+        if (set == null) {
+            set = new WeakSwitchPointSet();
+            this.switchPointMap.put(key, set);
+        }
+
+        set.add(switchPoint);
+    }
+
+    Set<SwitchPoint> getSwitchPoints(final Object key) {
+        WeakSwitchPointSet switchPointSet = switchPointMap.get(key);
+        if (switchPointSet != null) {
+            return switchPointSet.elements();
+        }
+
+        return Collections.emptySet();
+    }
+
+    /**
+     * Invalidate all switchpoints for the given property. This is called when that
+     * property is created, deleted, or modified in a script object.
+     *
+     * @param prop The property to invalidate.
+     */
+    synchronized void invalidateProperty(final Property prop) {
+        final WeakSwitchPointSet set = switchPointMap.get(prop.getKey());
+        if (set != null) {
+            if (Context.DEBUG) {
+                switchPointsInvalidated.add(set.size());
+            }
+            final SwitchPoint[] switchPoints = set.elements().toArray(EMPTY_SWITCHPOINT_ARRAY);
+            SwitchPoint.invalidateAll(switchPoints);
+            this.switchPointMap.remove(prop.getKey());
+        }
+    }
+
+
+    /**
+     * Invalidate all switchpoints except those defined in {@code map}. This is called
+     * when the prototype of a script object is changed.
+     *
+     * @param map map of properties to exclude from invalidation
+     */
+    synchronized void invalidateInheritedProperties(final PropertyMap map) {
+        for (final Map.Entry<Object, WeakSwitchPointSet> entry : switchPointMap.entrySet()) {
+            if (map.findProperty(entry.getKey()) != null) {
+                continue;
+            }
+            if (Context.DEBUG) {
+                switchPointsInvalidated.add(entry.getValue().size());
+            }
+            final SwitchPoint[] switchPoints = entry.getValue().elements().toArray(EMPTY_SWITCHPOINT_ARRAY);
+            SwitchPoint.invalidateAll(switchPoints);
+        }
+        switchPointMap.clear();
+    }
+
+    private static class WeakSwitchPointSet {
+
+        private final WeakHashMap<SwitchPoint, Void> map;
+
+        WeakSwitchPointSet() {
+            map = new WeakHashMap<>();
+        }
+
+        WeakSwitchPointSet(final WeakSwitchPointSet set) {
+            map = new WeakHashMap<>(set.map);
+        }
+
+        void add(final SwitchPoint switchPoint) {
+            map.put(switchPoint, null);
+        }
+
+        boolean contains(final SwitchPoint switchPoint) {
+            return map.containsKey(switchPoint);
+        }
+
+        Set<SwitchPoint> elements() {
+            return map.keySet();
+        }
+
+        int size() {
+            return map.size();
+        }
+
+    }
+}
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java	Tue Nov 07 16:19:55 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java	Wed Nov 08 12:15:24 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -1248,7 +1248,7 @@
             proto = newProto;
 
             // Let current listeners know that the prototype has changed
-            getMap().protoChanged(true);
+            getMap().protoChanged();
             // Replace our current allocator map with one that is associated with the new prototype.
             setMap(getMap().changeProto(newProto));
         }
@@ -2107,30 +2107,38 @@
     }
 
     /**
-     * Get a switch point for a property with the given {@code name} that will be invalidated when
-     * the property definition is changed in this object's prototype chain. Returns {@code null} if
-     * the property is defined in this object itself.
+     * Get an array of switch points for a property with the given {@code name} that will be
+     * invalidated when the property definition is changed in this object's prototype chain.
+     * Returns {@code null} if the property is defined in this object itself.
      *
      * @param name the property name
      * @param owner the property owner, null if property is not defined
-     * @return a SwitchPoint or null
+     * @return an array of SwitchPoints or null
      */
     public final SwitchPoint[] getProtoSwitchPoints(final String name, final ScriptObject owner) {
         if (owner == this || getProto() == null) {
             return null;
         }
 
-        final List<SwitchPoint> switchPoints = new ArrayList<>();
-        for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) {
-            final ScriptObject parent = obj.getProto();
-            parent.getMap().addListener(name, obj.getMap());
-            final SwitchPoint sp = parent.getMap().getSharedProtoSwitchPoint();
-            if (sp != null && !sp.hasBeenInvalidated()) {
-                switchPoints.add(sp);
+        final Set<SwitchPoint> switchPoints = new HashSet<>();
+        SwitchPoint switchPoint = getProto().getMap().getSwitchPoint(name);
+
+        if (switchPoint == null) {
+            switchPoint = new SwitchPoint();
+            for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) {
+                obj.getProto().getMap().addSwitchPoint(name, switchPoint);
             }
         }
 
-        switchPoints.add(getMap().getSwitchPoint(name));
+        switchPoints.add(switchPoint);
+
+        for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) {
+            final SwitchPoint sharedProtoSwitchPoint = obj.getProto().getMap().getSharedProtoSwitchPoint();
+            if (sharedProtoSwitchPoint != null && !sharedProtoSwitchPoint.hasBeenInvalidated()) {
+                switchPoints.add(sharedProtoSwitchPoint);
+            }
+        }
+
         return switchPoints.toArray(new SwitchPoint[0]);
     }
 
@@ -2141,12 +2149,16 @@
             return null;
         }
 
-        for (ScriptObject obj = this; obj.getProto() != null; obj = obj.getProto()) {
-            final ScriptObject parent = obj.getProto();
-            parent.getMap().addListener(name, obj.getMap());
+        SwitchPoint switchPoint = getProto().getMap().getSwitchPoint(name);
+
+        if (switchPoint == null) {
+            switchPoint = new SwitchPoint();
+            for (ScriptObject obj = this; obj.getProto() != null; obj = obj.getProto()) {
+                obj.getProto().getMap().addSwitchPoint(name, switchPoint);
+            }
         }
 
-        return getMap().getSwitchPoint(name);
+        return switchPoint;
     }
 
     private void checkSharedProtoMap() {
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java	Tue Nov 07 16:19:55 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java	Wed Nov 08 12:15:24 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -185,7 +185,7 @@
 
     private SetMethod createNewPropertySetter(final SwitchPoint builtinSwitchPoint) {
         final SetMethod sm = map.getFreeFieldSlot() > -1 ? createNewFieldSetter(builtinSwitchPoint) : createNewSpillPropertySetter(builtinSwitchPoint);
-        map.propertyAdded(sm.property, true);
+        map.propertyChanged(sm.property);
         return sm;
     }
 
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SharedPropertyMap.java	Tue Nov 07 16:19:55 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SharedPropertyMap.java	Wed Nov 08 12:15:24 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -48,33 +48,15 @@
      * Create a new shared property map from the given {@code map}.
      * @param map property map to copy
      */
-    public SharedPropertyMap(final PropertyMap map) {
+    SharedPropertyMap(final PropertyMap map) {
         super(map);
         this.switchPoint = new SwitchPoint();
     }
 
     @Override
-    public void propertyAdded(final Property property, final boolean isSelf) {
-        if (isSelf) {
-            invalidateSwitchPoint();
-        }
-        super.propertyAdded(property, isSelf);
-    }
-
-    @Override
-    public void propertyDeleted(final Property property, final boolean isSelf) {
-        if (isSelf) {
-            invalidateSwitchPoint();
-        }
-        super.propertyDeleted(property, isSelf);
-    }
-
-    @Override
-    public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) {
-        if (isSelf) {
-            invalidateSwitchPoint();
-        }
-        super.propertyModified(oldProperty, newProperty, isSelf);
+    public void propertyChanged(final Property property) {
+        invalidateSwitchPoint();
+        super.propertyChanged(property);
     }
 
     @Override